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
@@ -31,7 +31,7 @@ from zou.app.models.task import Task
31
31
  from zou.app.models.time_spent import TimeSpent
32
32
  from zou.app.models.working_file import WorkingFile
33
33
 
34
- from zou.app.utils import events, fields
34
+ from zou.app.utils import events, fields, date_helpers
35
35
  from zou.app.stores import file_store
36
36
  from zou.app import config
37
37
 
@@ -149,12 +149,12 @@ def remove_task(task_id, force=False):
149
149
  return task_serialized
150
150
 
151
151
 
152
- def remove_preview_file_by_id(preview_file_id):
152
+ def remove_preview_file_by_id(preview_file_id, force=False):
153
153
  preview_file = PreviewFile.get(preview_file_id)
154
- return remove_preview_file(preview_file)
154
+ return remove_preview_file(preview_file, force=force)
155
155
 
156
156
 
157
- def remove_preview_file(preview_file):
157
+ def remove_preview_file(preview_file, force=False):
158
158
  """
159
159
  Remove all files related to given preview file, then remove the preview file
160
160
  entry from the database.
@@ -169,13 +169,13 @@ def remove_preview_file(preview_file):
169
169
  if news is not None:
170
170
  news.update({"preview_file_id": None})
171
171
 
172
- # stop removing files for now
173
- # if preview_file.extension == "png":
174
- # clear_picture_files(preview_file.id)
175
- # elif preview_file.extension == "mp4":
176
- # clear_movie_files(preview_file.id)
177
- # else:
178
- # clear_generic_files(preview_file.id)
172
+ if config.REMOVE_FILES or force:
173
+ if preview_file.extension == "png":
174
+ clear_picture_files(preview_file.id)
175
+ elif preview_file.extension == "mp4":
176
+ clear_movie_files(preview_file.id)
177
+ else:
178
+ clear_generic_files(preview_file.id)
179
179
 
180
180
  preview_file.comments = []
181
181
  preview_file.save()
@@ -202,7 +202,9 @@ def remove_preview_file(preview_file):
202
202
  return preview_file.serialize()
203
203
 
204
204
 
205
- def remove_preview_background_file_by_id(preview_background_file_id):
205
+ def remove_preview_background_file_by_id(
206
+ preview_background_file_id, force=False
207
+ ):
206
208
  """
207
209
  Remove all files related to given preview background file, then remove the
208
210
  preview background file entry from the database.
@@ -210,16 +212,15 @@ def remove_preview_background_file_by_id(preview_background_file_id):
210
212
  preview_background_file = PreviewBackgroundFile.get(
211
213
  preview_background_file_id
212
214
  )
213
- return remove_preview_background_file(preview_background_file)
215
+ return remove_preview_background_file(preview_background_file, force=force)
214
216
 
215
217
 
216
- def remove_preview_background_file(preview_background_file):
218
+ def remove_preview_background_file(preview_background_file, force=False):
217
219
  """
218
220
  Remove all files related to given preview background file, then remove the
219
221
  preview background file entry from the database.
220
222
  """
221
- # stop removing files for now
222
- # clear_preview_background_files(preview_background_file.id)
223
+ clear_preview_background_files(preview_background_file.id, force=force)
223
224
  preview_background_file.delete()
224
225
  return preview_background_file.serialize()
225
226
 
@@ -240,25 +241,26 @@ def remove_attachment_file(attachment_file):
240
241
  Remove all files related to given attachment file, then remove the
241
242
  attachment file entry from the database.
242
243
  """
243
- # stop removing files for now
244
- # file_store.remove_file("attachments", str(attachment_file.id))
244
+ if config.REMOVE_FILES:
245
+ file_store.remove_file("attachments", str(attachment_file.id))
245
246
  attachment_dict = attachment_file.serialize()
246
247
  attachment_file.delete()
247
248
  return attachment_dict
248
249
 
249
250
 
250
- def clear_preview_background_files(preview_background_id):
251
+ def clear_preview_background_files(preview_background_id, force=False):
251
252
  """
252
253
  Remove all files related to given preview background file.
253
254
  """
254
- for image_type in [
255
- "thumbnails",
256
- "preview-backgrounds",
257
- ]:
258
- try:
259
- file_store.remove_picture(image_type, preview_background_id)
260
- except BaseException:
261
- pass
255
+ if config.REMOVE_FILES or force:
256
+ for image_type in [
257
+ "thumbnails",
258
+ "preview-backgrounds",
259
+ ]:
260
+ try:
261
+ file_store.remove_picture(image_type, preview_background_id)
262
+ except BaseException:
263
+ pass
262
264
 
263
265
 
264
266
  def clear_picture_files(preview_file_id):
@@ -360,6 +362,11 @@ def remove_project(project_id):
360
362
 
361
363
  ApiEvent.delete_all_by(project_id=project_id)
362
364
  Entity.delete_all_by(project_id=project_id)
365
+
366
+ descriptors = MetadataDescriptor.query.filter_by(project_id=project_id)
367
+ for descriptor in descriptors:
368
+ descriptor.departments = []
369
+ descriptor.save()
363
370
  MetadataDescriptor.delete_all_by(project_id=project_id)
364
371
  Milestone.delete_all_by(project_id=project_id)
365
372
  ScheduleItem.delete_all_by(project_id=project_id)
@@ -440,7 +447,9 @@ def remove_old_events(days_old=90):
440
447
  """
441
448
  Remove events older than *days_old*.
442
449
  """
443
- limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=days_old)
450
+ limit_date = date_helpers.get_utc_now_datetime() - datetime.timedelta(
451
+ days=days_old
452
+ )
444
453
  ApiEvent.query.filter(ApiEvent.created_at < limit_date).delete()
445
454
  ApiEvent.commit()
446
455
 
@@ -449,7 +458,9 @@ def remove_old_login_logs(days_old=90):
449
458
  """
450
459
  Remove login logs older than *days_old*.
451
460
  """
452
- limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=days_old)
461
+ limit_date = date_helpers.get_utc_now_datetime() - datetime.timedelta(
462
+ days=days_old
463
+ )
453
464
  LoginLog.query.filter(LoginLog.created_at < limit_date).delete()
454
465
  LoginLog.commit()
455
466
 
@@ -458,7 +469,9 @@ def remove_old_notifications(days_old=90):
458
469
  """
459
470
  Remove notifications older than *days_old*.
460
471
  """
461
- limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=days_old)
472
+ limit_date = date_helpers.get_utc_now_datetime() - datetime.timedelta(
473
+ days=days_old
474
+ )
462
475
  Notification.query.filter(Notification.created_at < limit_date).delete()
463
476
  Notification.commit()
464
477
 
@@ -33,7 +33,7 @@ from zou.app.services.exception import (
33
33
 
34
34
  def clear_edit_cache(edit_id):
35
35
  cache.cache.delete_memoized(get_edit, edit_id)
36
- cache.cache.delete_memoized(get_edit_with_relations, edit_id)
36
+ cache.cache.delete_memoized(get_edit, edit_id, True)
37
37
  cache.cache.delete_memoized(get_full_edit, edit_id)
38
38
 
39
39
 
@@ -241,19 +241,13 @@ def get_edit_raw(edit_id):
241
241
 
242
242
 
243
243
  @cache.memoize_function(120)
244
- def get_edit(edit_id):
244
+ def get_edit(edit_id, relations=False):
245
245
  """
246
246
  Return given edit as a dictionary.
247
247
  """
248
- return get_edit_raw(edit_id).serialize(obj_type="Edit")
249
-
250
-
251
- @cache.memoize_function(120)
252
- def get_edit_with_relations(edit_id):
253
- """
254
- Return given edit as a dictionary.
255
- """
256
- return get_edit_raw(edit_id).serialize(obj_type="Edit", relations=True)
248
+ return get_edit_raw(edit_id).serialize(
249
+ obj_type="Edit", relations=relations
250
+ )
257
251
 
258
252
 
259
253
  @cache.memoize_function(120)
@@ -37,7 +37,7 @@ def send_notification(person_id, subject, messages):
37
37
  )
38
38
 
39
39
  if person["notifications_slack_enabled"]:
40
- organisation = persons_service.get_organisation()
40
+ organisation = persons_service.get_organisation(sensitive=True)
41
41
  userid = person["notifications_slack_userid"]
42
42
  token = organisation.get("chat_token_slack", "")
43
43
  if config.ENABLE_JOB_QUEUE:
@@ -49,7 +49,7 @@ def send_notification(person_id, subject, messages):
49
49
  chats.send_to_slack(token, userid, slack_message)
50
50
 
51
51
  if person["notifications_mattermost_enabled"]:
52
- organisation = persons_service.get_organisation()
52
+ organisation = persons_service.get_organisation(sensitive=True)
53
53
  userid = person["notifications_mattermost_userid"]
54
54
  webhook = organisation.get("chat_webhook_mattermost", "")
55
55
  if config.ENABLE_JOB_QUEUE:
@@ -61,7 +61,7 @@ def send_notification(person_id, subject, messages):
61
61
  chats.send_to_mattermost(webhook, userid, mattermost_message)
62
62
 
63
63
  if person["notifications_discord_enabled"]:
64
- organisation = persons_service.get_organisation()
64
+ organisation = persons_service.get_organisation(sensitive=True)
65
65
  userid = person["notifications_discord_userid"]
66
66
  token = organisation.get("chat_token_discord", "")
67
67
  if config.ENABLE_JOB_QUEUE:
@@ -296,7 +296,7 @@ def get_task_descriptors(person_id, task):
296
296
  project = projects_service.get_project(task["project_id"])
297
297
  task_type = tasks_service.get_task_type(task["task_type_id"])
298
298
  entity = entities_service.get_entity(task["entity_id"])
299
- (entity_name, episode_id) = names_service.get_full_entity_name(
299
+ entity_name, episode_id, _ = names_service.get_full_entity_name(
300
300
  entity["id"]
301
301
  )
302
302
 
@@ -2,6 +2,9 @@ from zou.app.services import (
2
2
  base_service,
3
3
  projects_service,
4
4
  notifications_service,
5
+ assets_service,
6
+ shots_service,
7
+ edits_service,
5
8
  )
6
9
  from zou.app.utils import (
7
10
  date_helpers,
@@ -108,12 +111,13 @@ def update_entity_preview(entity_id, preview_file_id):
108
111
  if entity is None:
109
112
  raise EntityNotFoundException
110
113
 
114
+ entity_id = str(entity.id)
111
115
  preview_file = PreviewFile.get(preview_file_id)
112
116
  if preview_file is None:
113
117
  raise PreviewFileNotFoundException
114
118
 
115
119
  entity.update({"preview_file_id": preview_file.id})
116
- clear_entity_cache(str(entity.id))
120
+ clear_entity_cache(entity_id)
117
121
  events.emit(
118
122
  "preview-file:set-main",
119
123
  {"entity_id": entity_id, "preview_file_id": preview_file_id},
@@ -125,9 +129,14 @@ def update_entity_preview(entity_id, preview_file_id):
125
129
  entity_type_name = entity_type.name.lower()
126
130
  events.emit(
127
131
  "%s:update" % entity_type_name,
128
- {"%s_id" % entity_type_name: str(entity.id)},
132
+ {"%s_id" % entity_type_name: entity_id},
129
133
  project_id=str(entity.project_id),
130
134
  )
135
+ assets_service.clear_asset_cache(entity_id)
136
+ edits_service.clear_edit_cache(entity_id)
137
+ shots_service.clear_shot_cache(entity_id)
138
+ shots_service.clear_episode_cache(entity_id)
139
+ shots_service.clear_sequence_cache(entity_id)
131
140
  return entity.serialize()
132
141
 
133
142
 
@@ -240,7 +249,9 @@ def get_entities_and_tasks(criterions={}):
240
249
  Task.end_date,
241
250
  Task.start_date,
242
251
  Task.due_date,
252
+ Task.done_date,
243
253
  Task.last_comment_date,
254
+ Task.difficulty,
244
255
  assignees_table.columns.person,
245
256
  )
246
257
  )
@@ -269,7 +280,9 @@ def get_entities_and_tasks(criterions={}):
269
280
  task_end_date,
270
281
  task_start_date,
271
282
  task_due_date,
283
+ task_done_date,
272
284
  task_last_comment_date,
285
+ task_difficulty,
273
286
  person_id,
274
287
  ) in query.all():
275
288
  entity_id = str(entity.id)
@@ -305,6 +318,7 @@ def get_entities_and_tasks(criterions={}):
305
318
  "entity_id": entity_id,
306
319
  "end_date": task_end_date,
307
320
  "due_date": task_due_date,
321
+ "done_date": task_done_date,
308
322
  "duration": task_duration,
309
323
  "is_subscribed": subscription_map.get(task_id, False),
310
324
  "last_comment_date": task_last_comment_date,
@@ -312,6 +326,7 @@ def get_entities_and_tasks(criterions={}):
312
326
  "real_start_date": task_real_start_date,
313
327
  "retake_count": task_retake_count,
314
328
  "start_date": task_start_date,
329
+ "difficulty": task_difficulty,
315
330
  "task_status_id": str(task_status_id),
316
331
  "task_type_id": str(task_type_id),
317
332
  "assignees": [],
@@ -5,7 +5,12 @@ from sqlalchemy import func
5
5
 
6
6
 
7
7
  def get_last_events(
8
- after=None, before=None, page_size=100, only_files=False, project_id=None
8
+ after=None,
9
+ before=None,
10
+ limit=100,
11
+ only_files=False,
12
+ project_id=None,
13
+ name=None,
9
14
  ):
10
15
  """
11
16
  Return last 100 events published. If before parameter is set, it returns
@@ -38,7 +43,10 @@ def get_last_events(
38
43
  if project_id is not None:
39
44
  query = query.filter(ApiEvent.project_id == project_id)
40
45
 
41
- events = query.limit(page_size).all()
46
+ if name is not None:
47
+ query = query.filter(ApiEvent.name == name)
48
+
49
+ events = query.limit(limit).all()
42
50
  return [
43
51
  fields.serialize_dict(
44
52
  {
@@ -63,7 +71,7 @@ def create_login_log(person_id, ip_address, origin):
63
71
  return login_log.serialize()
64
72
 
65
73
 
66
- def get_last_login_logs(before=None, page_size=100):
74
+ def get_last_login_logs(before=None, limit=100):
67
75
  """
68
76
  Return last 100 login logs published. If before parameter is set, it returns
69
77
  last 100 login logs before this date.
@@ -77,7 +85,7 @@ def get_last_login_logs(before=None, page_size=100):
77
85
  LoginLog.created_at < func.cast(before, LoginLog.created_at.type)
78
86
  )
79
87
 
80
- login_logs = query.limit(page_size).all()
88
+ login_logs = query.limit(limit).all()
81
89
  return [
82
90
  {
83
91
  "created_at": fields.serialize_value(created_at),
@@ -49,6 +49,10 @@ class DepartmentNotFoundException(NotFound):
49
49
  pass
50
50
 
51
51
 
52
+ class StudioNotFoundException(NotFound):
53
+ pass
54
+
55
+
52
56
  class TaskStatusNotFoundException(NotFound):
53
57
  pass
54
58
 
@@ -247,7 +251,7 @@ class EntryAlreadyExistsException(Exception):
247
251
  pass
248
252
 
249
253
 
250
- class ArgumentsException(Exception):
254
+ class WrongParameterException(Exception):
251
255
  def __init__(self, message, dict=None):
252
256
  super().__init__(message)
253
257
  self.dict = dict
@@ -257,10 +261,6 @@ class WrongIdFormatException(Exception):
257
261
  pass
258
262
 
259
263
 
260
- class WrongParameterException(Exception):
261
- pass
262
-
263
-
264
264
  class ModelWithRelationsDeletionException(Exception):
265
265
  pass
266
266
 
@@ -1,5 +1,7 @@
1
1
  import slugify
2
2
 
3
+ from zou.app.utils import cache
4
+
3
5
  from zou.app.services import (
4
6
  entities_service,
5
7
  files_service,
@@ -10,6 +12,9 @@ from zou.app.services import (
10
12
  )
11
13
 
12
14
 
15
+ cache.memoize_function(1200)
16
+
17
+
13
18
  def get_full_entity_name(entity_id):
14
19
  """
15
20
  Get full entity name whether it's an asset or a shot. If it's a shot
@@ -47,7 +52,7 @@ def get_full_entity_name(entity_id):
47
52
  asset_type = entities_service.get_entity_type(entity["entity_type_id"])
48
53
  episode_id = entity["source_id"]
49
54
  name = "%s / %s" % (asset_type["name"], entity["name"])
50
- return (name, episode_id)
55
+ return (name, episode_id, entity["preview_file_id"])
51
56
 
52
57
 
53
58
  def get_preview_file_name(preview_file_id):
@@ -61,7 +66,7 @@ def get_preview_file_name(preview_file_id):
61
66
  task = tasks_service.get_task(preview_file["task_id"])
62
67
  task_type = tasks_service.get_task_type(task["task_type_id"])
63
68
  project = projects_service.get_project(task["project_id"])
64
- (entity_name, _) = get_full_entity_name(task["entity_id"])
69
+ (entity_name, _, _) = get_full_entity_name(task["entity_id"])
65
70
 
66
71
  if (
67
72
  organisation["use_original_file_name"]
@@ -92,16 +92,17 @@ def get_last_news_for_project(
92
92
  task_status_id=None,
93
93
  author_id=None,
94
94
  page=1,
95
- page_size=50,
95
+ limit=50,
96
96
  before=None,
97
97
  after=None,
98
98
  episode_id=None,
99
+ current_user=None,
99
100
  ):
100
101
  """
101
102
  Return last 50 news for given project. Add related information to make it
102
103
  displayable.
103
104
  """
104
- offset = (page - 1) * page_size
105
+ offset = (page - 1) * limit
105
106
 
106
107
  query = (
107
108
  News.query.order_by(News.created_at.desc())
@@ -120,6 +121,9 @@ def get_last_news_for_project(
120
121
 
121
122
  if len(project_ids) > 0:
122
123
  query = query.filter(Project.id.in_(project_ids))
124
+ elif current_user is not None:
125
+ if current_user.role.code != "admin":
126
+ query = query.filter(Project.team.contains(current_user))
123
127
 
124
128
  if entity_id is not None:
125
129
  query = query.filter(Entity.id == entity_id)
@@ -154,7 +158,7 @@ def get_last_news_for_project(
154
158
  News.created_at < func.cast(before, News.created_at.type)
155
159
  )
156
160
 
157
- (total, nb_pages) = _get_news_total(query, page_size)
161
+ (total, nb_pages) = _get_news_total(query, limit)
158
162
 
159
163
  query = query.add_columns(
160
164
  Project.id,
@@ -168,7 +172,7 @@ def get_last_news_for_project(
168
172
  Entity.preview_file_id,
169
173
  )
170
174
 
171
- query = query.limit(page_size)
175
+ query = query.limit(limit)
172
176
  query = query.offset(offset)
173
177
  news_list = query.all()
174
178
  result = []
@@ -185,7 +189,7 @@ def get_last_news_for_project(
185
189
  preview_file_annotations,
186
190
  entity_preview_file_id,
187
191
  ) in news_list:
188
- (full_entity_name, episode_id) = names_service.get_full_entity_name(
192
+ full_entity_name, episode_id, _ = names_service.get_full_entity_name(
189
193
  task_entity_id
190
194
  )
191
195
 
@@ -217,15 +221,15 @@ def get_last_news_for_project(
217
221
  "data": result,
218
222
  "total": total,
219
223
  "nb_pages": nb_pages,
220
- "limit": page_size,
224
+ "limit": limit,
221
225
  "offset": offset,
222
226
  "page": page,
223
227
  }
224
228
 
225
229
 
226
- def _get_news_total(query, page_size):
230
+ def _get_news_total(query, limit):
227
231
  total = query.count()
228
- nb_pages = int(math.ceil(total / float(page_size)))
232
+ nb_pages = int(math.ceil(total / float(limit)))
229
233
  return total, nb_pages
230
234
 
231
235
 
@@ -239,6 +243,7 @@ def get_news_stats_for_project(
239
243
  author_id=None,
240
244
  before=None,
241
245
  after=None,
246
+ current_user=None,
242
247
  ):
243
248
  """
244
249
  Return the number of news by task status for given project and filters.
@@ -262,6 +267,9 @@ def get_news_stats_for_project(
262
267
 
263
268
  if len(project_ids) > 0:
264
269
  query = query.filter(Project.id.in_(project_ids))
270
+ elif current_user is not None:
271
+ if current_user.role.code != "admin":
272
+ query = query.filter(Project.team.contains(current_user))
265
273
 
266
274
  if task_status_id is not None:
267
275
  query = query.filter(Comment.task_status_id == task_status_id)
@@ -306,4 +314,4 @@ def get_news_for_entity(entity_id):
306
314
  """
307
315
  Get all news related to a given entity.
308
316
  """
309
- return get_last_news_for_project(entity_id=entity_id, page_size=2000)
317
+ return get_last_news_for_project(entity_id=entity_id, limit=2000)
@@ -16,8 +16,8 @@ from zou.app.models.organisation import Organisation
16
16
  from zou.app.models.person import Person
17
17
  from zou.app.models.time_spent import TimeSpent
18
18
 
19
- from zou.app import config
20
- from zou.app.utils import fields, events, cache, emails
19
+ from zou.app import config, file_store
20
+ from zou.app.utils import fields, events, cache, emails, date_helpers
21
21
  from zou.app.services import index_service, auth_service
22
22
  from zou.app.stores import auth_tokens_store
23
23
  from zou.app.services.exception import (
@@ -36,8 +36,9 @@ def clear_person_cache():
36
36
  cache.cache.delete_memoized(get_persons)
37
37
 
38
38
 
39
- def clear_oranisation_cache():
39
+ def clear_organisation_cache():
40
40
  cache.cache.delete_memoized(get_organisation)
41
+ cache.cache.delete_memoized(get_organisation, True)
41
42
 
42
43
 
43
44
  @cache.memoize_function(120)
@@ -210,6 +211,7 @@ def create_person(
210
211
  ldap_uid=None,
211
212
  is_bot=False,
212
213
  expiration_date=None,
214
+ studio_id=None,
213
215
  active=True,
214
216
  serialize=True,
215
217
  ):
@@ -223,12 +225,14 @@ def create_person(
223
225
  if expiration_date is not None:
224
226
  try:
225
227
  if (
226
- datetime.datetime.strptime(expiration_date, "%Y-%m-%d").date()
228
+ date_helpers.get_date_from_string(expiration_date).date()
227
229
  < datetime.date.today()
228
230
  ):
229
231
  raise WrongParameterException(
230
232
  "Expiration date can't be in the past."
231
233
  )
234
+ except WrongParameterException:
235
+ raise
232
236
  except:
233
237
  raise WrongParameterException("Expiration date is not valid.")
234
238
 
@@ -245,6 +249,7 @@ def create_person(
245
249
  ldap_uid=ldap_uid,
246
250
  is_bot=is_bot,
247
251
  expiration_date=expiration_date,
252
+ studio_id=studio_id,
248
253
  active=active,
249
254
  )
250
255
  if is_bot:
@@ -274,18 +279,25 @@ def update_password(email, password):
274
279
  return person.serialize()
275
280
 
276
281
 
277
- def update_person(person_id, data):
282
+ def update_person(person_id, data, bypass_protected_accounts=False):
278
283
  """
279
284
  Update person entry with data given in parameter.
280
285
  """
281
286
  person = Person.get(person_id)
282
287
  if (
283
- data.get("active") is False
288
+ not bypass_protected_accounts
284
289
  and person.email in config.PROTECTED_ACCOUNTS
285
290
  ):
286
- raise PersonInProtectedAccounts(
287
- "Can't set this person as inactive it's a protected account."
288
- )
291
+ message = None
292
+ if data.get("active") is False:
293
+ message = (
294
+ "Can't set this person as inactive it's a protected account."
295
+ )
296
+ elif data.get("role") is not None:
297
+ message = "Can't change the role of this person it's a protected account."
298
+
299
+ if message is not None:
300
+ raise PersonInProtectedAccounts(message)
289
301
 
290
302
  if "email" in data and data["email"] is not None:
291
303
  data["email"] = data["email"].strip()
@@ -301,6 +313,8 @@ def update_person(person_id, data):
301
313
  raise WrongParameterException(
302
314
  "Expiration date can't be in the past."
303
315
  )
316
+ except WrongParameterException:
317
+ raise
304
318
  except:
305
319
  raise WrongParameterException("Expiration date is not valid.")
306
320
 
@@ -372,7 +386,9 @@ def update_person_last_presence(person_id):
372
386
  date = log.date
373
387
  elif time_spent is not None:
374
388
  date = time_spent.date
375
- return update_person(person_id, {"last_presence": date})
389
+ return update_person(
390
+ person_id, {"last_presence": date}, bypass_protected_accounts=True
391
+ )
376
392
 
377
393
 
378
394
  def get_presence_logs(year, month):
@@ -438,7 +454,7 @@ def invite_person(person_id):
438
454
  )
439
455
 
440
456
  time_string = format_datetime(
441
- datetime.datetime.utcnow(),
457
+ date_helpers.get_utc_now_datetime(),
442
458
  tzinfo=person["timezone"],
443
459
  locale=person["locale"],
444
460
  )
@@ -469,14 +485,14 @@ Thank you and see you soon on Kitsu,
469
485
 
470
486
 
471
487
  @cache.memoize_function(120)
472
- def get_organisation():
488
+ def get_organisation(sensitive=False):
473
489
  """
474
490
  Return organisation set up on this instance. It creates it if none exists.
475
491
  """
476
492
  organisation = Organisation.query.first()
477
493
  if organisation is None:
478
494
  organisation = Organisation.create(name="Kitsu")
479
- return organisation.present()
495
+ return organisation.present(sensitive=sensitive)
480
496
 
481
497
 
482
498
  def update_organisation(organisation_id, data):
@@ -486,7 +502,7 @@ def update_organisation(organisation_id, data):
486
502
  organisation = Organisation.get(organisation_id)
487
503
  organisation.update(data)
488
504
  events.emit("organisation:update", {"organisation_id": organisation_id})
489
- clear_oranisation_cache()
505
+ clear_organisation_cache()
490
506
  return organisation.present()
491
507
 
492
508
 
@@ -535,11 +551,11 @@ def clear_avatar(person_id):
535
551
  person = get_person_raw(person_id)
536
552
  person.update({"has_avatar": False})
537
553
  clear_person_cache()
538
- # stop removing files for now
539
- # try:
540
- # file_store.remove_picture("thumbnails", person_id)
541
- # except BaseException:
542
- # pass
554
+ if config.REMOVE_FILES:
555
+ try:
556
+ file_store.remove_picture("thumbnails", person_id)
557
+ except BaseException:
558
+ pass
543
559
  return person.serialize()
544
560
 
545
561
 
@@ -558,9 +574,10 @@ def create_access_token_for_raw_person(person):
558
574
  if person.expiration_date is not None:
559
575
  expires_delta = (
560
576
  datetime.datetime.combine(
561
- person.expiration_date, datetime.datetime.max.time()
577
+ person.expiration_date,
578
+ datetime.datetime.max.time(),
562
579
  )
563
- - datetime.datetime.utcnow()
580
+ - date_helpers.get_utc_now_datetime()
564
581
  )
565
582
  access_token = create_access_token(
566
583
  identity=person.id,