zou 0.20.44__tar.gz → 0.20.46__tar.gz

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 (466) hide show
  1. {zou-0.20.44/zou.egg-info → zou-0.20.46}/PKG-INFO +1 -1
  2. zou-0.20.46/zou/__init__.py +1 -0
  3. {zou-0.20.44 → zou-0.20.46}/zou/app/api.py +1 -43
  4. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/person.py +14 -0
  5. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/projects/__init__.py +5 -0
  6. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/projects/resources.py +32 -0
  7. {zou-0.20.44 → zou-0.20.46}/zou/app/config.py +1 -0
  8. {zou-0.20.44 → zou-0.20.46}/zou/app/models/plugin.py +1 -0
  9. zou-0.20.46/zou/app/services/plugins_service.py +66 -0
  10. {zou-0.20.44 → zou-0.20.46}/zou/app/services/time_spents_service.py +36 -0
  11. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/auth.py +6 -2
  12. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/commands.py +3 -0
  13. zou-0.20.46/zou/app/utils/plugins.py +340 -0
  14. {zou-0.20.44 → zou-0.20.46}/zou/cli.py +20 -7
  15. zou-0.20.46/zou/migrations/env.py +64 -0
  16. zou-0.20.46/zou/migrations/versions/d80f02824047_add_plugin_revision.py +67 -0
  17. zou-0.20.46/zou/plugin_template/migrations/alembic.ini +45 -0
  18. zou-0.20.46/zou/plugin_template/migrations/env.py +67 -0
  19. zou-0.20.46/zou/plugin_template/migrations/script.py.mako +25 -0
  20. zou-0.20.46/zou/plugin_template/models.py +17 -0
  21. zou-0.20.46/zou/plugin_template/routes.py +13 -0
  22. {zou-0.20.44 → zou-0.20.46/zou.egg-info}/PKG-INFO +1 -1
  23. {zou-0.20.44 → zou-0.20.46}/zou.egg-info/SOURCES.txt +5 -0
  24. zou-0.20.44/zou/__init__.py +0 -1
  25. zou-0.20.44/zou/app/services/plugins_service.py +0 -169
  26. zou-0.20.44/zou/app/utils/plugins.py +0 -88
  27. zou-0.20.44/zou/migrations/env.py +0 -108
  28. zou-0.20.44/zou/plugin_template/routes.py +0 -6
  29. {zou-0.20.44 → zou-0.20.46}/LICENSE +0 -0
  30. {zou-0.20.44 → zou-0.20.46}/README.rst +0 -0
  31. {zou-0.20.44 → zou-0.20.46}/pyproject.toml +0 -0
  32. {zou-0.20.44 → zou-0.20.46}/setup.cfg +0 -0
  33. {zou-0.20.44 → zou-0.20.46}/setup.py +0 -0
  34. {zou-0.20.44 → zou-0.20.46}/zou/app/__init__.py +0 -0
  35. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/__init__.py +0 -0
  36. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/assets/__init__.py +0 -0
  37. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/assets/resources.py +0 -0
  38. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/auth/__init__.py +0 -0
  39. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/auth/resources.py +0 -0
  40. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/breakdown/__init__.py +0 -0
  41. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/breakdown/resources.py +0 -0
  42. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/chats/__init__.py +0 -0
  43. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/chats/resources.py +0 -0
  44. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/comments/__init__.py +0 -0
  45. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/comments/resources.py +0 -0
  46. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/concepts/__init__.py +0 -0
  47. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/concepts/resources.py +0 -0
  48. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/__init__.py +0 -0
  49. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/asset_instance.py +0 -0
  50. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/attachment_file.py +0 -0
  51. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/base.py +0 -0
  52. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/budget.py +0 -0
  53. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/budget_entry.py +0 -0
  54. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/chat.py +0 -0
  55. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/chat_message.py +0 -0
  56. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/comments.py +0 -0
  57. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/custom_action.py +0 -0
  58. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/day_off.py +0 -0
  59. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/department.py +0 -0
  60. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/entity.py +0 -0
  61. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/entity_link.py +0 -0
  62. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/entity_type.py +0 -0
  63. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/event.py +0 -0
  64. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/file_status.py +0 -0
  65. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/metadata_descriptor.py +0 -0
  66. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/milestone.py +0 -0
  67. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/news.py +0 -0
  68. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/notification.py +0 -0
  69. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/organisation.py +0 -0
  70. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/output_file.py +0 -0
  71. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/output_type.py +0 -0
  72. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/playlist.py +0 -0
  73. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/plugin.py +0 -0
  74. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/preview_background_file.py +0 -0
  75. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/preview_file.py +0 -0
  76. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/project.py +0 -0
  77. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/project_status.py +0 -0
  78. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/salary_scale.py +0 -0
  79. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/schedule_item.py +0 -0
  80. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/search_filter.py +0 -0
  81. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/search_filter_group.py +0 -0
  82. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/software.py +0 -0
  83. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/status_automation.py +0 -0
  84. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/studio.py +0 -0
  85. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/subscription.py +0 -0
  86. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/task.py +0 -0
  87. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/task_status.py +0 -0
  88. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/task_type.py +0 -0
  89. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/time_spent.py +0 -0
  90. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/working_file.py +0 -0
  91. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/edits/__init__.py +0 -0
  92. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/edits/resources.py +0 -0
  93. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/entities/__init__.py +0 -0
  94. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/entities/resources.py +0 -0
  95. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/events/__init__.py +0 -0
  96. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/events/resources.py +0 -0
  97. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/__init__.py +0 -0
  98. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/__init__.py +0 -0
  99. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/assets.py +0 -0
  100. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/base.py +0 -0
  101. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/casting.py +0 -0
  102. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/edits.py +0 -0
  103. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/persons.py +0 -0
  104. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/playlists.py +0 -0
  105. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/projects.py +0 -0
  106. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/shots.py +0 -0
  107. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/task_types.py +0 -0
  108. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/tasks.py +0 -0
  109. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/time_spents.py +0 -0
  110. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/files/__init__.py +0 -0
  111. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/files/resources.py +0 -0
  112. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/index/__init__.py +0 -0
  113. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/index/resources.py +0 -0
  114. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/news/__init__.py +0 -0
  115. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/news/resources.py +0 -0
  116. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/persons/__init__.py +0 -0
  117. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/persons/resources.py +0 -0
  118. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/playlists/__init__.py +0 -0
  119. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/playlists/resources.py +0 -0
  120. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/previews/__init__.py +0 -0
  121. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/previews/resources.py +0 -0
  122. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/search/__init__.py +0 -0
  123. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/search/resources.py +0 -0
  124. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/shots/__init__.py +0 -0
  125. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/shots/resources.py +0 -0
  126. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/__init__.py +0 -0
  127. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/__init__.py +0 -0
  128. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/assets.py +0 -0
  129. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/base.py +0 -0
  130. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/casting.py +0 -0
  131. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/edits.py +0 -0
  132. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/persons.py +0 -0
  133. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/shots.py +0 -0
  134. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/task_type_estimations.py +0 -0
  135. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/kitsu.py +0 -0
  136. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/otio.py +0 -0
  137. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/__init__.py +0 -0
  138. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/assets.py +0 -0
  139. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/base.py +0 -0
  140. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/episode.py +0 -0
  141. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/exception.py +0 -0
  142. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/import_errors.py +0 -0
  143. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/notes.py +0 -0
  144. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/person.py +0 -0
  145. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/project.py +0 -0
  146. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/scene.py +0 -0
  147. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/sequence.py +0 -0
  148. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/shot.py +0 -0
  149. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/status.py +0 -0
  150. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/steps.py +0 -0
  151. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/tasks.py +0 -0
  152. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/team.py +0 -0
  153. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/versions.py +0 -0
  154. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/tasks/__init__.py +0 -0
  155. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/tasks/resources.py +0 -0
  156. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/user/__init__.py +0 -0
  157. {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/user/resources.py +0 -0
  158. {zou-0.20.44 → zou-0.20.46}/zou/app/file_trees/default.json +0 -0
  159. {zou-0.20.44 → zou-0.20.46}/zou/app/file_trees/simple.json +0 -0
  160. {zou-0.20.44 → zou-0.20.46}/zou/app/indexer/__init__.py +0 -0
  161. {zou-0.20.44 → zou-0.20.46}/zou/app/indexer/indexing.py +0 -0
  162. {zou-0.20.44 → zou-0.20.46}/zou/app/mixin.py +0 -0
  163. {zou-0.20.44 → zou-0.20.46}/zou/app/models/__init__.py +0 -0
  164. {zou-0.20.44 → zou-0.20.46}/zou/app/models/asset_instance.py +0 -0
  165. {zou-0.20.44 → zou-0.20.46}/zou/app/models/attachment_file.py +0 -0
  166. {zou-0.20.44 → zou-0.20.46}/zou/app/models/base.py +0 -0
  167. {zou-0.20.44 → zou-0.20.46}/zou/app/models/budget.py +0 -0
  168. {zou-0.20.44 → zou-0.20.46}/zou/app/models/budget_entry.py +0 -0
  169. {zou-0.20.44 → zou-0.20.46}/zou/app/models/build_job.py +0 -0
  170. {zou-0.20.44 → zou-0.20.46}/zou/app/models/chat.py +0 -0
  171. {zou-0.20.44 → zou-0.20.46}/zou/app/models/chat_message.py +0 -0
  172. {zou-0.20.44 → zou-0.20.46}/zou/app/models/comment.py +0 -0
  173. {zou-0.20.44 → zou-0.20.46}/zou/app/models/custom_action.py +0 -0
  174. {zou-0.20.44 → zou-0.20.46}/zou/app/models/data_import_error.py +0 -0
  175. {zou-0.20.44 → zou-0.20.46}/zou/app/models/day_off.py +0 -0
  176. {zou-0.20.44 → zou-0.20.46}/zou/app/models/department.py +0 -0
  177. {zou-0.20.44 → zou-0.20.46}/zou/app/models/desktop_login_log.py +0 -0
  178. {zou-0.20.44 → zou-0.20.46}/zou/app/models/entity.py +0 -0
  179. {zou-0.20.44 → zou-0.20.46}/zou/app/models/entity_type.py +0 -0
  180. {zou-0.20.44 → zou-0.20.46}/zou/app/models/event.py +0 -0
  181. {zou-0.20.44 → zou-0.20.46}/zou/app/models/file_status.py +0 -0
  182. {zou-0.20.44 → zou-0.20.46}/zou/app/models/login_log.py +0 -0
  183. {zou-0.20.44 → zou-0.20.46}/zou/app/models/metadata_descriptor.py +0 -0
  184. {zou-0.20.44 → zou-0.20.46}/zou/app/models/milestone.py +0 -0
  185. {zou-0.20.44 → zou-0.20.46}/zou/app/models/news.py +0 -0
  186. {zou-0.20.44 → zou-0.20.46}/zou/app/models/notification.py +0 -0
  187. {zou-0.20.44 → zou-0.20.46}/zou/app/models/organisation.py +0 -0
  188. {zou-0.20.44 → zou-0.20.46}/zou/app/models/output_file.py +0 -0
  189. {zou-0.20.44 → zou-0.20.46}/zou/app/models/output_type.py +0 -0
  190. {zou-0.20.44 → zou-0.20.46}/zou/app/models/person.py +0 -0
  191. {zou-0.20.44 → zou-0.20.46}/zou/app/models/playlist.py +0 -0
  192. {zou-0.20.44 → zou-0.20.46}/zou/app/models/preview_background_file.py +0 -0
  193. {zou-0.20.44 → zou-0.20.46}/zou/app/models/preview_file.py +0 -0
  194. {zou-0.20.44 → zou-0.20.46}/zou/app/models/project.py +0 -0
  195. {zou-0.20.44 → zou-0.20.46}/zou/app/models/project_status.py +0 -0
  196. {zou-0.20.44 → zou-0.20.46}/zou/app/models/salary_scale.py +0 -0
  197. {zou-0.20.44 → zou-0.20.46}/zou/app/models/schedule_item.py +0 -0
  198. {zou-0.20.44 → zou-0.20.46}/zou/app/models/search_filter.py +0 -0
  199. {zou-0.20.44 → zou-0.20.46}/zou/app/models/search_filter_group.py +0 -0
  200. {zou-0.20.44 → zou-0.20.46}/zou/app/models/serializer.py +0 -0
  201. {zou-0.20.44 → zou-0.20.46}/zou/app/models/software.py +0 -0
  202. {zou-0.20.44 → zou-0.20.46}/zou/app/models/status_automation.py +0 -0
  203. {zou-0.20.44 → zou-0.20.46}/zou/app/models/studio.py +0 -0
  204. {zou-0.20.44 → zou-0.20.46}/zou/app/models/subscription.py +0 -0
  205. {zou-0.20.44 → zou-0.20.46}/zou/app/models/task.py +0 -0
  206. {zou-0.20.44 → zou-0.20.46}/zou/app/models/task_status.py +0 -0
  207. {zou-0.20.44 → zou-0.20.46}/zou/app/models/task_type.py +0 -0
  208. {zou-0.20.44 → zou-0.20.46}/zou/app/models/time_spent.py +0 -0
  209. {zou-0.20.44 → zou-0.20.46}/zou/app/models/working_file.py +0 -0
  210. {zou-0.20.44 → zou-0.20.46}/zou/app/services/__init__.py +0 -0
  211. {zou-0.20.44 → zou-0.20.46}/zou/app/services/assets_service.py +0 -0
  212. {zou-0.20.44 → zou-0.20.46}/zou/app/services/auth_service.py +0 -0
  213. {zou-0.20.44 → zou-0.20.46}/zou/app/services/backup_service.py +0 -0
  214. {zou-0.20.44 → zou-0.20.46}/zou/app/services/base_service.py +0 -0
  215. {zou-0.20.44 → zou-0.20.46}/zou/app/services/breakdown_service.py +0 -0
  216. {zou-0.20.44 → zou-0.20.46}/zou/app/services/budget_service.py +0 -0
  217. {zou-0.20.44 → zou-0.20.46}/zou/app/services/chats_service.py +0 -0
  218. {zou-0.20.44 → zou-0.20.46}/zou/app/services/comments_service.py +0 -0
  219. {zou-0.20.44 → zou-0.20.46}/zou/app/services/concepts_service.py +0 -0
  220. {zou-0.20.44 → zou-0.20.46}/zou/app/services/custom_actions_service.py +0 -0
  221. {zou-0.20.44 → zou-0.20.46}/zou/app/services/deletion_service.py +0 -0
  222. {zou-0.20.44 → zou-0.20.46}/zou/app/services/edits_service.py +0 -0
  223. {zou-0.20.44 → zou-0.20.46}/zou/app/services/emails_service.py +0 -0
  224. {zou-0.20.44 → zou-0.20.46}/zou/app/services/entities_service.py +0 -0
  225. {zou-0.20.44 → zou-0.20.46}/zou/app/services/events_service.py +0 -0
  226. {zou-0.20.44 → zou-0.20.46}/zou/app/services/exception.py +0 -0
  227. {zou-0.20.44 → zou-0.20.46}/zou/app/services/file_tree_service.py +0 -0
  228. {zou-0.20.44 → zou-0.20.46}/zou/app/services/files_service.py +0 -0
  229. {zou-0.20.44 → zou-0.20.46}/zou/app/services/index_service.py +0 -0
  230. {zou-0.20.44 → zou-0.20.46}/zou/app/services/names_service.py +0 -0
  231. {zou-0.20.44 → zou-0.20.46}/zou/app/services/news_service.py +0 -0
  232. {zou-0.20.44 → zou-0.20.46}/zou/app/services/notifications_service.py +0 -0
  233. {zou-0.20.44 → zou-0.20.46}/zou/app/services/persons_service.py +0 -0
  234. {zou-0.20.44 → zou-0.20.46}/zou/app/services/playlists_service.py +0 -0
  235. {zou-0.20.44 → zou-0.20.46}/zou/app/services/preview_files_service.py +0 -0
  236. {zou-0.20.44 → zou-0.20.46}/zou/app/services/projects_service.py +0 -0
  237. {zou-0.20.44 → zou-0.20.46}/zou/app/services/scenes_service.py +0 -0
  238. {zou-0.20.44 → zou-0.20.46}/zou/app/services/schedule_service.py +0 -0
  239. {zou-0.20.44 → zou-0.20.46}/zou/app/services/shots_service.py +0 -0
  240. {zou-0.20.44 → zou-0.20.46}/zou/app/services/stats_service.py +0 -0
  241. {zou-0.20.44 → zou-0.20.46}/zou/app/services/status_automations_service.py +0 -0
  242. {zou-0.20.44 → zou-0.20.46}/zou/app/services/sync_service.py +0 -0
  243. {zou-0.20.44 → zou-0.20.46}/zou/app/services/tasks_service.py +0 -0
  244. {zou-0.20.44 → zou-0.20.46}/zou/app/services/telemetry_services.py +0 -0
  245. {zou-0.20.44 → zou-0.20.46}/zou/app/services/user_service.py +0 -0
  246. {zou-0.20.44 → zou-0.20.46}/zou/app/stores/__init__.py +0 -0
  247. {zou-0.20.44 → zou-0.20.46}/zou/app/stores/auth_tokens_store.py +0 -0
  248. {zou-0.20.44 → zou-0.20.46}/zou/app/stores/file_store.py +0 -0
  249. {zou-0.20.44 → zou-0.20.46}/zou/app/stores/publisher_store.py +0 -0
  250. {zou-0.20.44 → zou-0.20.46}/zou/app/stores/queue_store.py +0 -0
  251. {zou-0.20.44 → zou-0.20.46}/zou/app/swagger.py +0 -0
  252. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/__init__.py +0 -0
  253. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/api.py +0 -0
  254. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/cache.py +0 -0
  255. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/chats.py +0 -0
  256. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/colors.py +0 -0
  257. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/csv_utils.py +0 -0
  258. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/date_helpers.py +0 -0
  259. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/dbhelpers.py +0 -0
  260. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/emails.py +0 -0
  261. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/env.py +0 -0
  262. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/events.py +0 -0
  263. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/fido.py +0 -0
  264. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/fields.py +0 -0
  265. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/flask.py +0 -0
  266. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/fs.py +0 -0
  267. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/git.py +0 -0
  268. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/logs.py +0 -0
  269. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/monitoring.py +0 -0
  270. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/permissions.py +0 -0
  271. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/query.py +0 -0
  272. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/redis.py +0 -0
  273. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/remote_job.py +0 -0
  274. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/saml.py +0 -0
  275. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/shell.py +0 -0
  276. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/string.py +0 -0
  277. {zou-0.20.44 → zou-0.20.46}/zou/app/utils/thumbnail.py +0 -0
  278. {zou-0.20.44 → zou-0.20.46}/zou/debug.py +0 -0
  279. {zou-0.20.44 → zou-0.20.46}/zou/event_stream.py +0 -0
  280. {zou-0.20.44 → zou-0.20.46}/zou/job_settings.py +0 -0
  281. {zou-0.20.44 → zou-0.20.46}/zou/migrations/README +0 -0
  282. {zou-0.20.44 → zou-0.20.46}/zou/migrations/__init__.py +0 -0
  283. {zou-0.20.44 → zou-0.20.46}/zou/migrations/alembic.ini +0 -0
  284. {zou-0.20.44 → zou-0.20.46}/zou/migrations/script.py.mako +0 -0
  285. {zou-0.20.44 → zou-0.20.46}/zou/migrations/utils/__init__.py +0 -0
  286. {zou-0.20.44 → zou-0.20.46}/zou/migrations/utils/base.py +0 -0
  287. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/003be8a91001_add_start_and_end_dates_to_projects.py +0 -0
  288. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/0596674df51d_add_department_mentions_to_comments.py +0 -0
  289. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/05ac7e8caa21_remove_unique_constraint_for_taskstatus_.py +0 -0
  290. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/05b7dc79a416_add_archived_fields_to_main_tables.py +0 -0
  291. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/06552e22f9e7_add_comment_updated_by.py +0 -0
  292. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/0cf5e0e035fa_drop_column_tasktype_for_shots.py +0 -0
  293. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/0ec3762a745d_add_attachment_table.py +0 -0
  294. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/0ef6416a507b_.py +0 -0
  295. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/10cf267d95c9_fix_schedule_item.py +0 -0
  296. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/16328eae4b5f_add_new_constraint_for_timespent.py +0 -0
  297. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/16df47d76c64_add_some_indexes.py +0 -0
  298. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/17ef8f7be758_disallow_null_choicetype.py +0 -0
  299. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1bb55759146f_add_table_studio.py +0 -0
  300. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1cb44194db49_add_file_size_field_to_preview_file.py +0 -0
  301. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1e150c2cea4d_add_nb_entities_out.py +0 -0
  302. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1e2d77a2f0c4_add_hd_by_default_column.py +0 -0
  303. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +0 -0
  304. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/20a8ad264659_tasktypeassettypelink_.py +0 -0
  305. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/20dfeb36142b_add_projecttaskstatuslink_roles_for_.py +0 -0
  306. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +0 -0
  307. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/269d41bfb73f_add_entity_entity_links.py +0 -0
  308. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/2762a797f1f9_add_people_salary_information.py +0 -0
  309. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/29df910f04a4_create_unique_constraint_project_id_.py +0 -0
  310. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/29fe01a6c9eb_add_status_automation_field.py +0 -0
  311. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/2adc020885fa_.py +0 -0
  312. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/2baede80b111_add_entity_status_field.py +0 -0
  313. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/306266361f4f_.py +0 -0
  314. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/307edd8c639d_change_comment_updated_by_in_comment_.py +0 -0
  315. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/328fd44c6347_add_entity_created_by.py +0 -0
  316. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +0 -0
  317. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/346250b5304c_add_position_to_preview_files.py +0 -0
  318. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3476e147e632_add_acks_to_comments.py +0 -0
  319. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/389cfb9de776_.py +0 -0
  320. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/38baa9a23b3d_.py +0 -0
  321. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/398150912a3f_validation_status_preview.py +0 -0
  322. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3b0d1321079e_.py +0 -0
  323. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3d5c93bafb9d_.py +0 -0
  324. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3e0538ddf80f_.py +0 -0
  325. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3fee3bd10f9d_.py +0 -0
  326. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4095103c7d01_add_is_clients_isolated_flag_to_projects.py +0 -0
  327. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/40dea9555940_.py +0 -0
  328. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/42ec83db6a01_change_person_preferred_two_factor_.py +0 -0
  329. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/43d0cf0ed5e7_.py +0 -0
  330. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/443d1e78a932_.py +0 -0
  331. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/45c2de366e66_.py +0 -0
  332. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/45dafbb3f4e1_for_person_contract_type_disallow_null.py +0 -0
  333. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/45f739ef962a_add_people_salary_scale_table.py +0 -0
  334. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4715c2586036_add_last_preview_file_fields.py +0 -0
  335. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4aab1f84ad72_introduce_plugin_table.py +0 -0
  336. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4e3738cdc34c_.py +0 -0
  337. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4f2398ebcd49_.py +0 -0
  338. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/523ee9647bee_.py +0 -0
  339. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/539a3a00c417_for_departmentlink_index_person_id_.py +0 -0
  340. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/54ee0d1d60ba_add_build_job_model.py +0 -0
  341. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/556526e47daa_.py +0 -0
  342. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +0 -0
  343. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5798d2c9020b_change_person_role_to_choicetype.py +0 -0
  344. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/590aa1ffe731_add_notifications_enabled_flag.py +0 -0
  345. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +0 -0
  346. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5a291251823c_add_max_retake_parameter.py +0 -0
  347. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5ab9d7a75887_.py +0 -0
  348. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5b0fcbb94f24_.py +0 -0
  349. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5b7fa3e51701_add_is_client_allowed_flag_to_task_.py +0 -0
  350. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5b980f0dc365_add_comment_links.py +0 -0
  351. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5b9fd9ddfe43_add_homepage_and_contract_fields.py +0 -0
  352. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5c0498e264bc_add_slack_fields.py +0 -0
  353. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5e2ce62632a6_add_workflow_to_project.py +0 -0
  354. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +0 -0
  355. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/693cc511d28d_add_taskstatus_priority.py +0 -0
  356. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6aa446ee4072_add_is_for_all_flage_to_playlists.py +0 -0
  357. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6bd3b102d61b_.py +0 -0
  358. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6c597e842afa_add_task_type_field_to_playlists.py +0 -0
  359. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6d1b2c60f58b_add_milestone_model.py +0 -0
  360. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6d7fa5a8e9a5_add_timesheets_locked_field_to_org.py +0 -0
  361. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6eeaff945706_add_data_field_on_entity_links.py +0 -0
  362. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6f6049877105_.py +0 -0
  363. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7417c8eb70d8_add_for_entity_field_to_playlists.py +0 -0
  364. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/772a5e43f05b_.py +0 -0
  365. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7748d3d22925_add_columns_to_searchfilter_table_to_.py +0 -0
  366. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/77d6820f494f_add_reply_to_notif.py +0 -0
  367. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7a16258f2fab_add_currency_field_to_budgets.py +0 -0
  368. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7b1f765677d8_.py +0 -0
  369. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7bc746997e8d_add_slack_token_field_to_organisation.py +0 -0
  370. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7dc79d4ed7cd_.py +0 -0
  371. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/818f7bda2528_.py +0 -0
  372. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/82e7f7a95e84_add_project_id_to_events.py +0 -0
  373. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/82ee682204ab_add_is_generated_from_ldap_column_to_.py +0 -0
  374. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/83e2f33a9b14_add_project_bugdet_table.py +0 -0
  375. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8588f254d6b8_add_archived_fields.py +0 -0
  376. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8739ae9fa28b_add_for_client_field.py +0 -0
  377. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/87efceb6745b_add_ready_for_to_entity.py +0 -0
  378. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/892b264937ec_.py +0 -0
  379. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8a1b4a1b7f4a_add_totp_columns_for_person.py +0 -0
  380. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8ab98c178903_add_budget_entry_table.py +0 -0
  381. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8e4f39e321f4_add_day_off_table.py +0 -0
  382. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8e67c183bed7_add_preference_fields.py +0 -0
  383. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8fbd40afbe5f_allow_to_set_float_values_for_.py +0 -0
  384. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9010a64e5a2d_add_indices.py +0 -0
  385. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9060dd4f6116_notification_uc.py +0 -0
  386. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/925771029620_.py +0 -0
  387. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +0 -0
  388. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/92bdfe07e5f5_discord_integration.py +0 -0
  389. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/956659992419_add_columns_for_person_to_store_fido_.py +0 -0
  390. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/96c79d31e648_add_mail_otp_columns_for_person.py +0 -0
  391. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/96f58a4a2a58_person_partial_index_for_email_only_.py +0 -0
  392. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +0 -0
  393. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/98c90621cf58_add_for_client_flag_to_playlist.py +0 -0
  394. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/99825b9cc778_.py +0 -0
  395. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9a09467f9b2c_.py +0 -0
  396. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +0 -0
  397. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9bd17364fc18_.py +0 -0
  398. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +0 -0
  399. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9e5b3a9b0cee_add_new_column_metadatadescriptor_data_type_.py +0 -0
  400. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9f8445f9b42c_add_man_days_fields.py +0 -0
  401. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a23682ccc1f1_.py +0 -0
  402. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +0 -0
  403. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a519c710877c_.py +0 -0
  404. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a65bdadbae2f_.py +0 -0
  405. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a66508788c53_add_nb_assets_ready.py +0 -0
  406. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a6c25eed3ea1_add_login_failed_attemps_and_last_login_.py +0 -0
  407. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a7c43f3fbc76_add_duration_column_to_the_preview_file.py +0 -0
  408. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/aa0a60033106_feedback_request.py +0 -0
  409. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/addbad59c706_allow_to_manage_drawings_instead_of_.py +0 -0
  410. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/addbbefa7028_add_departments_link_to_metadata_.py +0 -0
  411. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/ae0127f2fc56_add_previewfile_width_and_previewfile_.py +0 -0
  412. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/af1790868e2c_.py +0 -0
  413. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b45cb782bb9c_.py +0 -0
  414. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b4dd0add5f79_.py +0 -0
  415. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b8c0a0f9d054_drop_task_status_is_reviewable.py +0 -0
  416. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b8ed0fb263f8_add_person_jti_and_jti_expiration_date.py +0 -0
  417. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b97a71306fc8_add_is_casting_standby_column_to_entity.py +0 -0
  418. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +0 -0
  419. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/bf1347acdee2_.py +0 -0
  420. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/c49e41f1298b_add_previewbackground.py +0 -0
  421. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/c68c2a62cfac_add_mimetype_column_to_attachment.py +0 -0
  422. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/c726b98be194_.py +0 -0
  423. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/c81f3e83bdb5_.py +0 -0
  424. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +0 -0
  425. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/cf3d365de164_add_entity_version_model.py +0 -0
  426. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/cf6cec6d6bf5_add_status_field_to_preview_file.py +0 -0
  427. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/d25118cddcaa_modify_salary_scale_model.py +0 -0
  428. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/d80267806131_task_status_new_column_is_default.py +0 -0
  429. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/d8dcd5196d57_add_casting_label.py +0 -0
  430. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/de8a3de227ef_.py +0 -0
  431. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/deeacd38d373_for_projecttaskstatuslink_set_default_.py +0 -0
  432. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/df1834485f57_.py +0 -0
  433. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/df9f8a147e80_change_file_size_to_big_integer.py +0 -0
  434. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e1ef93f40d3d_.py +0 -0
  435. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e29638428dfd_add_schedule_item_table.py +0 -0
  436. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e3f6db74cc1e_.py +0 -0
  437. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e7e633bd6fa2_add_exceptions_to_budget_entries.py +0 -0
  438. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e839d6603c09_add_person_id_to_shot_history.py +0 -0
  439. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/ee2373fbe3a4_.py +0 -0
  440. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f0567e8d0c62_.py +0 -0
  441. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f0c6cbb61869_add_production_style_field.py +0 -0
  442. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +0 -0
  443. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f4ff5a73d283_add_person_ldap_uid.py +0 -0
  444. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f5b113876a49_add_preferred_two_factor_authentication_.py +0 -0
  445. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f5bdca075cdc_add_preview_download_flag_to_projects.py +0 -0
  446. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f874ad5e898a_add_link_entity_type_task_type.py +0 -0
  447. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f995b28fb749_.py +0 -0
  448. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fb6b6f188497_.py +0 -0
  449. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fb87feaaa094_add_missing_unique_constraints.py +0 -0
  450. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fba149993140_add_missing_index.py +0 -0
  451. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fc322f908695_.py +0 -0
  452. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fee7c696166e_.py +0 -0
  453. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/feffd3c5b806_introduce_concepts.py +0 -0
  454. {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/ffeed4956ab1_add_more_details_to_projects.py +0 -0
  455. {zou-0.20.44 → zou-0.20.46}/zou/plugin_template/__init__.py +0 -0
  456. {zou-0.20.44 → zou-0.20.46}/zou/remote/__init__.py +0 -0
  457. {zou-0.20.44 → zou-0.20.46}/zou/remote/config_payload.py +0 -0
  458. {zou-0.20.44 → zou-0.20.46}/zou/remote/normalize_movie.py +0 -0
  459. {zou-0.20.44 → zou-0.20.46}/zou/remote/playlist.py +0 -0
  460. {zou-0.20.44 → zou-0.20.46}/zou/utils/__init__.py +0 -0
  461. {zou-0.20.44 → zou-0.20.46}/zou/utils/movie.py +0 -0
  462. {zou-0.20.44 → zou-0.20.46}/zou.egg-info/dependency_links.txt +0 -0
  463. {zou-0.20.44 → zou-0.20.46}/zou.egg-info/entry_points.txt +0 -0
  464. {zou-0.20.44 → zou-0.20.46}/zou.egg-info/not-zip-safe +0 -0
  465. {zou-0.20.44 → zou-0.20.46}/zou.egg-info/requires.txt +0 -0
  466. {zou-0.20.44 → zou-0.20.46}/zou.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zou
3
- Version: 0.20.44
3
+ Version: 0.20.46
4
4
  Summary: API to store and manage the data of your animation production
5
5
  Home-page: https://zou.cg-wire.com
6
6
  Author: CG Wire
@@ -0,0 +1 @@
1
+ __version__ = "0.20.46"
@@ -1,9 +1,4 @@
1
- import os
2
1
  import sys
3
- import importlib
4
- import traceback
5
-
6
- from pathlib import Path
7
2
 
8
3
  from zou.app.blueprints.assets import blueprint as assets_blueprint
9
4
  from zou.app.blueprints.auth import blueprint as auth_blueprint
@@ -29,7 +24,7 @@ from zou.app.blueprints.user import blueprint as user_blueprint
29
24
  from zou.app.blueprints.edits import blueprint as edits_blueprint
30
25
  from zou.app.blueprints.concepts import blueprint as concepts_blueprint
31
26
 
32
- from zou.app.utils.plugins import PluginManifest
27
+ from zou.app.utils.plugins import load_plugins
33
28
  from zou.app.utils import events
34
29
 
35
30
 
@@ -92,40 +87,3 @@ def register_event_handlers(app):
92
87
  # app.logger.info("No event handlers folder is configured.")
93
88
  pass
94
89
  return app
95
-
96
-
97
- def load_plugins(app):
98
- """
99
- Load plugins from the plugin folder.
100
- """
101
- plugin_folder = app.config["PLUGIN_FOLDER"]
102
- abs_plugin_path = os.path.abspath(plugin_folder)
103
- if abs_plugin_path not in sys.path:
104
- sys.path.insert(0, abs_plugin_path)
105
-
106
- if os.path.exists(plugin_folder):
107
- for plugin_id in os.listdir(plugin_folder):
108
- try:
109
- load_plugin(app, plugin_id)
110
- app.logger.info(f"Plugin {plugin_id} loaded.")
111
- except ImportError as e:
112
- app.logger.error(f"Plugin {plugin_id} failed to import: {e}")
113
- except Exception as e:
114
- app.logger.error(
115
- f"Plugin {plugin_id} failed to initialize: {e}"
116
- )
117
- app.logger.debug(traceback.format_exc())
118
-
119
- if abs_plugin_path in sys.path:
120
- sys.path.remove(abs_plugin_path)
121
-
122
-
123
- def load_plugin(app, plugin_id):
124
- plugin_path = Path(app.config["PLUGIN_FOLDER"]) / plugin_id
125
- manifest = PluginManifest.from_file(plugin_path / "manifest.toml")
126
-
127
- plugin_module = importlib.import_module(plugin_id)
128
- if hasattr(plugin_module, "init_plugin"):
129
- plugin_module.init_plugin(app, manifest)
130
-
131
- return plugin_module
@@ -101,6 +101,13 @@ class PersonsResource(BaseModelsResource):
101
101
  raise
102
102
  except:
103
103
  raise WrongParameterException("Expiration date is not valid.")
104
+
105
+ if "email" in data:
106
+ try:
107
+ data["email"] = auth.validate_email(data["email"])
108
+ except auth.EmailNotValidException as e:
109
+ raise WrongParameterException(str(e))
110
+
104
111
  return data
105
112
 
106
113
  def update_data(self, data):
@@ -184,6 +191,13 @@ class PersonResource(BaseModelResource, ArgsMixin):
184
191
  raise
185
192
  except:
186
193
  raise WrongParameterException("Expiration date is not valid.")
194
+
195
+ if "email" in data:
196
+ try:
197
+ data["email"] = auth.validate_email(data["email"])
198
+ except auth.EmailNotValidException as e:
199
+ raise WrongParameterException(str(e))
200
+
187
201
  return data
188
202
 
189
203
  def check_delete_permissions(self, instance_dict):
@@ -30,6 +30,7 @@ from zou.app.blueprints.projects.resources import (
30
30
  ProductionBudgetResource,
31
31
  ProductionBudgetEntriesResource,
32
32
  ProductionBudgetEntryResource,
33
+ ProductionMonthTimeSpentsResource,
33
34
  )
34
35
 
35
36
  routes = [
@@ -127,6 +128,10 @@ routes = [
127
128
  "/data/projects/<project_id>/budgets/<budget_id>/entries/<entry_id>",
128
129
  ProductionBudgetEntryResource,
129
130
  ),
131
+ (
132
+ "/data/projects/<project_id>/budgets/time-spents",
133
+ ProductionMonthTimeSpentsResource,
134
+ ),
130
135
  ]
131
136
 
132
137
  blueprint = Blueprint("projects", "projects")
@@ -5,9 +5,11 @@ from flask_jwt_extended import jwt_required
5
5
  from zou.app.services import budget_service
6
6
  from zou.app.mixin import ArgsMixin
7
7
  from zou.app.services import (
8
+ persons_service,
8
9
  projects_service,
9
10
  schedule_service,
10
11
  tasks_service,
12
+ time_spents_service,
11
13
  user_service,
12
14
  )
13
15
  from zou.app.utils import permissions
@@ -1494,3 +1496,33 @@ class ProductionBudgetEntryResource(Resource, ArgsMixin):
1494
1496
  user_service.check_manager_project_access(project_id)
1495
1497
  budget_service.delete_budget_entry(entry_id)
1496
1498
  return "", 204
1499
+
1500
+
1501
+ class ProductionMonthTimeSpentsResource(Resource, ArgsMixin):
1502
+
1503
+ @jwt_required()
1504
+ def get(self, project_id):
1505
+ """
1506
+ Get aggregated time spents by month for given project.
1507
+ ---
1508
+ tags:
1509
+ - Projects
1510
+ parameters:
1511
+ - in: path
1512
+ name: project_id
1513
+ required: True
1514
+ type: string
1515
+ format: UUID
1516
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
1517
+ responses:
1518
+ 200:
1519
+ description: Aggregated time spents for given person and month
1520
+ 400:
1521
+ description: Wrong ID format
1522
+ """
1523
+ permissions.check_admin_permissions()
1524
+ self.check_id_parameter(project_id)
1525
+ user = persons_service.get_current_user()
1526
+ return time_spents_service.get_project_month_time_spents(
1527
+ project_id, user["timezone"]
1528
+ )
@@ -88,6 +88,7 @@ MAIL_USE_SSL = envtobool("MAIL_USE_SSL", False)
88
88
  MAIL_DEFAULT_SENDER = os.getenv(
89
89
  "MAIL_DEFAULT_SENDER", "no-reply@your-studio.com"
90
90
  )
91
+ MAIL_CHECK_DELIVERABILITY = envtobool("MAIL_CHECK_DELIVERABILITY", True)
91
92
  DOMAIN_NAME = os.getenv("DOMAIN_NAME", "localhost:8080")
92
93
  DOMAIN_PROTOCOL = os.getenv("DOMAIN_PROTOCOL", "https")
93
94
 
@@ -19,3 +19,4 @@ class Plugin(db.Model, BaseMixin, SerializerMixin):
19
19
  maintainer_email = db.Column(EmailType)
20
20
  website = db.Column(URLType)
21
21
  license = db.Column(db.String(80), nullable=False)
22
+ revision = db.Column(db.String(12), nullable=True)
@@ -0,0 +1,66 @@
1
+ import semver
2
+ from pathlib import Path
3
+
4
+ from zou.app import config, db
5
+ from zou.app.models.plugin import Plugin
6
+ from zou.app.utils.plugins import (
7
+ PluginManifest,
8
+ run_plugin_migrations,
9
+ downgrade_plugin_migrations,
10
+ uninstall_plugin_files,
11
+ install_plugin_files,
12
+ )
13
+
14
+
15
+ def install_plugin(path, force=False):
16
+ """
17
+ Install a plugin.
18
+ """
19
+ path = Path(path)
20
+ if not path.exists():
21
+ raise FileNotFoundError(f"Plugin path '{path}' does not exist.")
22
+
23
+ manifest = PluginManifest.from_plugin_path(path)
24
+ plugin = Plugin.query.filter_by(plugin_id=manifest.id).one_or_none()
25
+
26
+ try:
27
+ if plugin:
28
+ current = semver.Version.parse(plugin.version)
29
+ new = semver.Version.parse(str(manifest.version))
30
+ if not force and new <= current:
31
+ raise ValueError(
32
+ f"Plugin version {new} is not newer than {current}."
33
+ )
34
+ plugin.update_no_commit(manifest.to_model_dict())
35
+ else:
36
+ plugin = Plugin.create_no_commit(**manifest.to_model_dict())
37
+
38
+ plugin_path = install_plugin_files(
39
+ path, Path(config.PLUGIN_FOLDER) / manifest.id
40
+ )
41
+ run_plugin_migrations(plugin_path, plugin)
42
+ except Exception:
43
+ uninstall_plugin_files(manifest.id)
44
+ db.session.rollback()
45
+ db.session.remove()
46
+ raise
47
+
48
+ Plugin.commit()
49
+ return plugin.serialize()
50
+
51
+
52
+ def uninstall_plugin(plugin_id):
53
+ """
54
+ Uninstall a plugin.
55
+ """
56
+ plugin_path = Path(config.PLUGIN_FOLDER) / plugin_id
57
+ downgrade_plugin_migrations(plugin_path)
58
+ installed = uninstall_plugin_files(plugin_path)
59
+ plugin = Plugin.query.filter_by(plugin_id=plugin_id).one_or_none()
60
+ if plugin is not None:
61
+ installed = True
62
+ plugin.delete()
63
+
64
+ if not installed:
65
+ raise ValueError(f"Plugin '{plugin_id}' is not installed.")
66
+ return True
@@ -7,6 +7,7 @@ from sqlalchemy.exc import DataError
7
7
  from sqlalchemy.orm import aliased
8
8
 
9
9
  from zou.app.models.day_off import DayOff
10
+ from zou.app.models.department import Department
10
11
  from zou.app.models.project import Project
11
12
  from zou.app.models.task import Task
12
13
  from zou.app.models.task_type import TaskType
@@ -535,3 +536,38 @@ def get_timezoned_interval(start, end):
535
536
  """
536
537
  timezone = user_service.get_timezone()
537
538
  return date_helpers.get_timezoned_interval(start, end, timezone)
539
+
540
+
541
+ def get_project_month_time_spents(project_id, timezone=None):
542
+ """
543
+ Get aggregated time spents by department by person by month for given
544
+ project.
545
+ """
546
+ data = {}
547
+ query = (
548
+ TimeSpent.query
549
+ .join(Task)
550
+ .join(TaskType, TaskType.id == Task.task_type_id)
551
+ .join(Department, Department.id == TaskType.department_id)
552
+ .filter(Task.project_id == project_id)
553
+ .add_columns(Department.id)
554
+ .order_by(TimeSpent.date)
555
+ )
556
+
557
+ for time_spent, department_id in query.all():
558
+ date_key = date_helpers.get_simple_string_with_timezone_from_date(
559
+ time_spent.date, timezone
560
+ )[0:7]
561
+ if department_id not in data:
562
+ data[department_id] = { "total": 0 }
563
+ if time_spent.person_id not in data[department_id]:
564
+ data[department_id][time_spent.person_id] = { "total": 0 }
565
+ if date_key not in data[department_id][time_spent.person_id]:
566
+ data[department_id][time_spent.person_id][date_key] = 0
567
+
568
+ data[department_id][time_spent.person_id][date_key] += \
569
+ time_spent.duration
570
+ data[department_id]["total"] += time_spent.duration
571
+ data[department_id][time_spent.person_id]["total"] += \
572
+ time_spent.duration
573
+ return data
@@ -23,9 +23,13 @@ def encrypt_password(password):
23
23
  return flask_bcrypt.generate_password_hash(password)
24
24
 
25
25
 
26
- def validate_email(email):
26
+ def validate_email(
27
+ email, check_deliverability=config.MAIL_CHECK_DELIVERABILITY
28
+ ):
27
29
  try:
28
- return email_validator.validate_email(email).normalized
30
+ return email_validator.validate_email(
31
+ email, check_deliverability=check_deliverability
32
+ ).normalized
29
33
  except email_validator.EmailNotValidError as e:
30
34
  raise EmailNotValidException(str(e))
31
35
 
@@ -865,6 +865,9 @@ def list_plugins(output_format, verbose, filter_field, filter_value):
865
865
  if verbose:
866
866
  plugin_data["Description"] = plugin.description or "-"
867
867
  plugin_data["Website"] = plugin.website or "-"
868
+ plugin_data["Revision"] = plugin.revision or "-"
869
+ plugin_data["Installation Date"] = plugin.created_at
870
+ plugin_data["Last Update"] = plugin.updated_at
868
871
  plugin_list.append(plugin_data)
869
872
 
870
873
  if output_format == "table":
@@ -0,0 +1,340 @@
1
+ import tomlkit
2
+ import semver
3
+ import email.utils
4
+ import spdx_license_list
5
+ import zipfile
6
+ import importlib
7
+ import importlib.util
8
+ import sys
9
+ import os
10
+ import traceback
11
+ import shutil
12
+
13
+ from pathlib import Path
14
+ from flask import current_app
15
+ from alembic import command
16
+ from alembic.config import Config
17
+
18
+
19
+ from pathlib import Path
20
+ from collections.abc import MutableMapping
21
+
22
+
23
+ class PluginManifest(MutableMapping):
24
+ def __init__(self, data):
25
+ super().__setattr__("data", data)
26
+ self.validate()
27
+
28
+ @classmethod
29
+ def from_plugin_path(cls, path):
30
+ path = Path(path)
31
+ if path.is_dir():
32
+ return cls.from_file(path / "manifest.toml")
33
+ elif zipfile.is_zipfile(path):
34
+ with zipfile.ZipFile(path) as z:
35
+ with z.open("manifest.toml") as f:
36
+ data = tomlkit.load(f)
37
+ return cls(data)
38
+ else:
39
+ raise ValueError(f"Invalid plugin path: {path}")
40
+
41
+ @classmethod
42
+ def from_file(cls, path):
43
+ with open(path, "rb") as f:
44
+ data = tomlkit.load(f)
45
+ return cls(data)
46
+
47
+ def write_to_path(self, path):
48
+ path = Path(path)
49
+ with open(path / "manifest.toml", "w", encoding="utf-8") as f:
50
+ tomlkit.dump(self.data, f)
51
+
52
+ def validate(self):
53
+ semver.Version.parse(str(self.data["version"]))
54
+ spdx_license_list.LICENSES[self.data["license"]]
55
+ if "maintainer" in self.data:
56
+ name, email_addr = email.utils.parseaddr(self.data["maintainer"])
57
+ self.data["maintainer_name"] = name
58
+ self.data["maintainer_email"] = email_addr
59
+
60
+ def to_model_dict(self):
61
+ return {
62
+ "plugin_id": self.data["id"],
63
+ "name": self.data["name"],
64
+ "description": self.data.get("description"),
65
+ "version": str(self.data["version"]),
66
+ "maintainer_name": self.data.get("maintainer_name"),
67
+ "maintainer_email": self.data.get("maintainer_email"),
68
+ "website": self.data.get("website"),
69
+ "license": self.data["license"],
70
+ }
71
+
72
+ def __getitem__(self, key):
73
+ return self.data[key]
74
+
75
+ def __setitem__(self, key, value):
76
+ self.data[key] = value
77
+
78
+ def __delitem__(self, key):
79
+ del self.data[key]
80
+
81
+ def __iter__(self):
82
+ return iter(self.data)
83
+
84
+ def __len__(self):
85
+ return len(self.data)
86
+
87
+ def __repr__(self):
88
+ return f"<PluginManifest {self.data!r}>"
89
+
90
+ def __getattr__(self, attr):
91
+ try:
92
+ return self.data[attr]
93
+ except KeyError:
94
+ raise AttributeError(f"'PluginManifest' has no attribute '{attr}'")
95
+
96
+ def __setattr__(self, attr, value):
97
+ if attr == "data":
98
+ super().__setattr__(attr, value)
99
+ else:
100
+ self.data[attr] = value
101
+
102
+
103
+ def load_plugin(app, plugin_path, init_plugin=True):
104
+ """
105
+ Load a plugin from the plugin folder.
106
+ """
107
+ plugin_path = Path(plugin_path)
108
+ manifest = PluginManifest.from_plugin_path(plugin_path)
109
+
110
+ plugin_module = importlib.import_module(manifest["id"])
111
+ if init_plugin and hasattr(plugin_module, "init_plugin"):
112
+ plugin_module.init_plugin(app, manifest)
113
+
114
+ return plugin_module
115
+
116
+
117
+ def load_plugins(app):
118
+ """
119
+ Load plugins from the plugin folder.
120
+ """
121
+ plugin_folder = Path(app.config["PLUGIN_FOLDER"])
122
+ if plugin_folder.exists():
123
+ abs_plugin_path = str(plugin_folder.absolute())
124
+ if abs_plugin_path not in sys.path:
125
+ sys.path.insert(0, abs_plugin_path)
126
+
127
+ for plugin_id in os.listdir(plugin_folder):
128
+ try:
129
+ load_plugin(app, plugin_folder / plugin_id)
130
+ app.logger.info(f"Plugin {plugin_id} loaded.")
131
+ except ImportError as e:
132
+ app.logger.error(f"Plugin {plugin_id} failed to import: {e}")
133
+ except Exception as e:
134
+ app.logger.error(
135
+ f"Plugin {plugin_id} failed to initialize: {e}"
136
+ )
137
+ app.logger.debug(traceback.format_exc())
138
+
139
+ if abs_plugin_path in sys.path:
140
+ sys.path.remove(abs_plugin_path)
141
+
142
+
143
+ def migrate_plugin_db(plugin_path, message):
144
+ """
145
+ Generates Alembic migration files in path/migrations.
146
+ """
147
+ plugin_path = Path(plugin_path).absolute()
148
+ models_path = plugin_path / "models.py"
149
+
150
+ if not models_path.exists():
151
+ raise FileNotFoundError(f"'models.py' not found in '{plugin_path}'")
152
+
153
+ manifest = PluginManifest.from_plugin_path(plugin_path)
154
+
155
+ module_name = f"_plugin_models_{manifest['id']}"
156
+ spec = importlib.util.spec_from_file_location(module_name, models_path)
157
+ if spec is None or spec.loader is None:
158
+ raise ImportError(f"Could not load 'models.py' from '{plugin_path}'")
159
+
160
+ module = importlib.util.module_from_spec(spec)
161
+ sys.modules[module_name] = module
162
+ try:
163
+ spec.loader.exec_module(module)
164
+ migrations_dir = plugin_path / "migrations"
165
+ versions_dir = migrations_dir / "versions"
166
+ versions_dir.mkdir(parents=True, exist_ok=True)
167
+
168
+ alembic_cfg = Config()
169
+ alembic_cfg.config_file_name = str(
170
+ plugin_path / "migrations" / "alembic.ini"
171
+ )
172
+ alembic_cfg.set_main_option("script_location", str(migrations_dir))
173
+ alembic_cfg.set_main_option(
174
+ "sqlalchemy.url", current_app.config["SQLALCHEMY_DATABASE_URI"]
175
+ )
176
+
177
+ command.revision(alembic_cfg, autogenerate=True, message=message)
178
+ finally:
179
+ del sys.modules[module_name]
180
+
181
+
182
+ def run_plugin_migrations(plugin_path, plugin):
183
+ """
184
+ Run plugin migrations.
185
+ """
186
+ plugin_path = Path(plugin_path)
187
+
188
+ alembic_cfg = Config()
189
+ alembic_cfg.config_file_name = str(
190
+ plugin_path / "migrations" / "alembic.ini"
191
+ )
192
+ alembic_cfg.set_main_option(
193
+ "script_location", str(plugin_path / "migrations")
194
+ )
195
+ alembic_cfg.set_main_option(
196
+ "sqlalchemy.url", current_app.config["SQLALCHEMY_DATABASE_URI"]
197
+ )
198
+
199
+ command.upgrade(alembic_cfg, "head")
200
+
201
+ script = command.ScriptDirectory.from_config(alembic_cfg)
202
+ head_revision = script.get_current_head()
203
+
204
+ plugin.revision = head_revision
205
+
206
+ return head_revision
207
+
208
+
209
+ def downgrade_plugin_migrations(plugin_path):
210
+ """
211
+ Downgrade plugin migrations to base.
212
+ """
213
+ plugin_path = Path(plugin_path)
214
+ manifest = PluginManifest.from_plugin_path(plugin_path)
215
+
216
+ alembic_cfg = Config()
217
+ alembic_cfg.config_file_name = str(
218
+ plugin_path / "migrations" / "alembic.ini"
219
+ )
220
+ alembic_cfg.set_main_option(
221
+ "script_location", str(plugin_path / "migrations")
222
+ )
223
+ alembic_cfg.set_main_option(
224
+ "sqlalchemy.url", current_app.config["SQLALCHEMY_DATABASE_URI"]
225
+ )
226
+
227
+ try:
228
+ command.downgrade(alembic_cfg, "base")
229
+ except Exception as e:
230
+ current_app.logger.warning(
231
+ f"Downgrade failed for plugin {manifest.id}: {e}"
232
+ )
233
+
234
+
235
+ def create_plugin_package(path, output_path, force=False):
236
+ """
237
+ Create a plugin package.
238
+ """
239
+ path = Path(path)
240
+ if not path.exists():
241
+ raise FileNotFoundError(f"Plugin path '{path}' does not exist.")
242
+
243
+ manifest = PluginManifest.from_plugin_path(path)
244
+
245
+ output_path = Path(output_path)
246
+ if not output_path.suffix == ".zip":
247
+ output_path /= f"{manifest.id}-{manifest.version}.zip"
248
+ if output_path.exists():
249
+ if force:
250
+ output_path.unlink()
251
+ else:
252
+ raise FileExistsError(
253
+ f"Output path '{output_path}' already exists."
254
+ )
255
+
256
+ output_path = shutil.make_archive(
257
+ output_path.with_suffix(""),
258
+ "zip",
259
+ path,
260
+ )
261
+ return output_path
262
+
263
+
264
+ def create_plugin_skeleton(
265
+ path,
266
+ id,
267
+ name,
268
+ description=None,
269
+ version=None,
270
+ maintainer=None,
271
+ website=None,
272
+ license=None,
273
+ force=False,
274
+ ):
275
+ plugin_template_path = (
276
+ Path(__file__).parent.parent.parent / "plugin_template"
277
+ )
278
+ plugin_path = Path(path) / id
279
+
280
+ if plugin_path.exists():
281
+ if force:
282
+ shutil.rmtree(plugin_path)
283
+ else:
284
+ raise FileExistsError(
285
+ f"Plugin '{id}' already exists in {plugin_path}."
286
+ )
287
+
288
+ shutil.copytree(plugin_template_path, plugin_path)
289
+
290
+ manifest = PluginManifest.from_file(plugin_path / "manifest.toml")
291
+
292
+ manifest.id = id
293
+ manifest.name = name
294
+ if description:
295
+ manifest.description = description
296
+ if version:
297
+ manifest.version = version
298
+ if maintainer:
299
+ manifest.maintainer = maintainer
300
+ if website:
301
+ manifest.website = website
302
+ if license:
303
+ manifest.license = license
304
+
305
+ manifest.validate()
306
+ manifest.write_to_path(plugin_path)
307
+
308
+ return plugin_path
309
+
310
+
311
+ def install_plugin_files(files_path, installation_path):
312
+ """
313
+ Install plugin files.
314
+ """
315
+ files_path = Path(files_path)
316
+ installation_path = Path(installation_path)
317
+
318
+ installation_path.mkdir(parents=True, exist_ok=True)
319
+
320
+ if files_path.is_dir():
321
+ shutil.copytree(files_path, installation_path, dirs_exist_ok=True)
322
+ elif zipfile.is_zipfile(files_path):
323
+ shutil.unpack_archive(files_path, installation_path, format="zip")
324
+ else:
325
+ raise ValueError(
326
+ f"Plugin path '{files_path}' is not a valid zip file or a directory."
327
+ )
328
+
329
+ return installation_path
330
+
331
+
332
+ def uninstall_plugin_files(plugin_path):
333
+ """
334
+ Uninstall plugin files.
335
+ """
336
+ plugin_path = Path(plugin_path)
337
+ if plugin_path.exists():
338
+ shutil.rmtree(plugin_path)
339
+ return True
340
+ return False