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.
- {zou-0.20.44/zou.egg-info → zou-0.20.46}/PKG-INFO +1 -1
- zou-0.20.46/zou/__init__.py +1 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/api.py +1 -43
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/person.py +14 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/projects/__init__.py +5 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/projects/resources.py +32 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/config.py +1 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/plugin.py +1 -0
- zou-0.20.46/zou/app/services/plugins_service.py +66 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/time_spents_service.py +36 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/auth.py +6 -2
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/commands.py +3 -0
- zou-0.20.46/zou/app/utils/plugins.py +340 -0
- {zou-0.20.44 → zou-0.20.46}/zou/cli.py +20 -7
- zou-0.20.46/zou/migrations/env.py +64 -0
- zou-0.20.46/zou/migrations/versions/d80f02824047_add_plugin_revision.py +67 -0
- zou-0.20.46/zou/plugin_template/migrations/alembic.ini +45 -0
- zou-0.20.46/zou/plugin_template/migrations/env.py +67 -0
- zou-0.20.46/zou/plugin_template/migrations/script.py.mako +25 -0
- zou-0.20.46/zou/plugin_template/models.py +17 -0
- zou-0.20.46/zou/plugin_template/routes.py +13 -0
- {zou-0.20.44 → zou-0.20.46/zou.egg-info}/PKG-INFO +1 -1
- {zou-0.20.44 → zou-0.20.46}/zou.egg-info/SOURCES.txt +5 -0
- zou-0.20.44/zou/__init__.py +0 -1
- zou-0.20.44/zou/app/services/plugins_service.py +0 -169
- zou-0.20.44/zou/app/utils/plugins.py +0 -88
- zou-0.20.44/zou/migrations/env.py +0 -108
- zou-0.20.44/zou/plugin_template/routes.py +0 -6
- {zou-0.20.44 → zou-0.20.46}/LICENSE +0 -0
- {zou-0.20.44 → zou-0.20.46}/README.rst +0 -0
- {zou-0.20.44 → zou-0.20.46}/pyproject.toml +0 -0
- {zou-0.20.44 → zou-0.20.46}/setup.cfg +0 -0
- {zou-0.20.44 → zou-0.20.46}/setup.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/assets/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/assets/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/auth/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/auth/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/breakdown/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/breakdown/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/chats/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/chats/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/comments/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/comments/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/concepts/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/concepts/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/asset_instance.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/attachment_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/base.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/budget.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/budget_entry.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/chat.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/chat_message.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/comments.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/custom_action.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/day_off.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/department.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/entity.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/entity_link.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/entity_type.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/event.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/file_status.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/metadata_descriptor.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/milestone.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/news.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/notification.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/organisation.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/output_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/output_type.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/playlist.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/plugin.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/preview_background_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/preview_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/project.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/project_status.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/salary_scale.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/schedule_item.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/search_filter.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/search_filter_group.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/software.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/status_automation.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/studio.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/subscription.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/task.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/task_status.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/task_type.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/time_spent.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/crud/working_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/edits/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/edits/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/entities/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/entities/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/events/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/events/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/assets.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/base.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/casting.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/edits.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/persons.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/playlists.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/projects.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/shots.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/task_types.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/tasks.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/export/csv/time_spents.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/files/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/files/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/index/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/index/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/news/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/news/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/persons/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/persons/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/playlists/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/playlists/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/previews/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/previews/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/search/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/search/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/shots/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/shots/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/assets.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/base.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/casting.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/edits.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/persons.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/shots.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/csv/task_type_estimations.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/kitsu.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/otio.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/assets.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/base.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/episode.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/exception.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/import_errors.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/notes.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/person.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/project.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/scene.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/sequence.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/shot.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/status.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/steps.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/tasks.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/team.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/source/shotgun/versions.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/tasks/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/tasks/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/user/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/blueprints/user/resources.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/file_trees/default.json +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/file_trees/simple.json +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/indexer/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/indexer/indexing.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/mixin.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/asset_instance.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/attachment_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/base.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/budget.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/budget_entry.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/build_job.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/chat.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/chat_message.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/comment.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/custom_action.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/data_import_error.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/day_off.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/department.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/desktop_login_log.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/entity.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/entity_type.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/event.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/file_status.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/login_log.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/metadata_descriptor.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/milestone.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/news.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/notification.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/organisation.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/output_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/output_type.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/person.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/playlist.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/preview_background_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/preview_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/project.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/project_status.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/salary_scale.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/schedule_item.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/search_filter.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/search_filter_group.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/serializer.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/software.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/status_automation.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/studio.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/subscription.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/task.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/task_status.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/task_type.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/time_spent.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/models/working_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/assets_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/auth_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/backup_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/base_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/breakdown_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/budget_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/chats_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/comments_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/concepts_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/custom_actions_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/deletion_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/edits_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/emails_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/entities_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/events_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/exception.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/file_tree_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/files_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/index_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/names_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/news_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/notifications_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/persons_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/playlists_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/preview_files_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/projects_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/scenes_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/schedule_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/shots_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/stats_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/status_automations_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/sync_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/tasks_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/telemetry_services.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/services/user_service.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/stores/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/stores/auth_tokens_store.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/stores/file_store.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/stores/publisher_store.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/stores/queue_store.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/swagger.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/api.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/cache.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/chats.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/colors.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/csv_utils.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/date_helpers.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/dbhelpers.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/emails.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/env.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/events.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/fido.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/fields.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/flask.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/fs.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/git.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/logs.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/monitoring.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/permissions.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/query.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/redis.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/remote_job.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/saml.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/shell.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/string.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/app/utils/thumbnail.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/debug.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/event_stream.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/job_settings.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/README +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/alembic.ini +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/script.py.mako +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/utils/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/utils/base.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/003be8a91001_add_start_and_end_dates_to_projects.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/0596674df51d_add_department_mentions_to_comments.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/05ac7e8caa21_remove_unique_constraint_for_taskstatus_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/05b7dc79a416_add_archived_fields_to_main_tables.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/06552e22f9e7_add_comment_updated_by.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/0cf5e0e035fa_drop_column_tasktype_for_shots.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/0ec3762a745d_add_attachment_table.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/0ef6416a507b_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/10cf267d95c9_fix_schedule_item.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/16328eae4b5f_add_new_constraint_for_timespent.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/16df47d76c64_add_some_indexes.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/17ef8f7be758_disallow_null_choicetype.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1bb55759146f_add_table_studio.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1cb44194db49_add_file_size_field_to_preview_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1e150c2cea4d_add_nb_entities_out.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1e2d77a2f0c4_add_hd_by_default_column.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/20a8ad264659_tasktypeassettypelink_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/20dfeb36142b_add_projecttaskstatuslink_roles_for_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/269d41bfb73f_add_entity_entity_links.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/2762a797f1f9_add_people_salary_information.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/29df910f04a4_create_unique_constraint_project_id_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/29fe01a6c9eb_add_status_automation_field.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/2adc020885fa_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/2baede80b111_add_entity_status_field.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/306266361f4f_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/307edd8c639d_change_comment_updated_by_in_comment_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/328fd44c6347_add_entity_created_by.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/346250b5304c_add_position_to_preview_files.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3476e147e632_add_acks_to_comments.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/389cfb9de776_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/38baa9a23b3d_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/398150912a3f_validation_status_preview.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3b0d1321079e_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3d5c93bafb9d_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3e0538ddf80f_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/3fee3bd10f9d_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4095103c7d01_add_is_clients_isolated_flag_to_projects.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/40dea9555940_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/42ec83db6a01_change_person_preferred_two_factor_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/43d0cf0ed5e7_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/443d1e78a932_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/45c2de366e66_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/45dafbb3f4e1_for_person_contract_type_disallow_null.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/45f739ef962a_add_people_salary_scale_table.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4715c2586036_add_last_preview_file_fields.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4aab1f84ad72_introduce_plugin_table.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4e3738cdc34c_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/4f2398ebcd49_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/523ee9647bee_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/539a3a00c417_for_departmentlink_index_person_id_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/54ee0d1d60ba_add_build_job_model.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/556526e47daa_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5798d2c9020b_change_person_role_to_choicetype.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/590aa1ffe731_add_notifications_enabled_flag.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5a291251823c_add_max_retake_parameter.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5ab9d7a75887_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5b0fcbb94f24_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5b7fa3e51701_add_is_client_allowed_flag_to_task_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5b980f0dc365_add_comment_links.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5b9fd9ddfe43_add_homepage_and_contract_fields.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5c0498e264bc_add_slack_fields.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/5e2ce62632a6_add_workflow_to_project.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/693cc511d28d_add_taskstatus_priority.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6aa446ee4072_add_is_for_all_flage_to_playlists.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6bd3b102d61b_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6c597e842afa_add_task_type_field_to_playlists.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6d1b2c60f58b_add_milestone_model.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6d7fa5a8e9a5_add_timesheets_locked_field_to_org.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6eeaff945706_add_data_field_on_entity_links.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/6f6049877105_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7417c8eb70d8_add_for_entity_field_to_playlists.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/772a5e43f05b_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7748d3d22925_add_columns_to_searchfilter_table_to_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/77d6820f494f_add_reply_to_notif.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7a16258f2fab_add_currency_field_to_budgets.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7b1f765677d8_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7bc746997e8d_add_slack_token_field_to_organisation.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/7dc79d4ed7cd_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/818f7bda2528_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/82e7f7a95e84_add_project_id_to_events.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/82ee682204ab_add_is_generated_from_ldap_column_to_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/83e2f33a9b14_add_project_bugdet_table.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8588f254d6b8_add_archived_fields.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8739ae9fa28b_add_for_client_field.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/87efceb6745b_add_ready_for_to_entity.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/892b264937ec_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8a1b4a1b7f4a_add_totp_columns_for_person.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8ab98c178903_add_budget_entry_table.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8e4f39e321f4_add_day_off_table.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8e67c183bed7_add_preference_fields.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/8fbd40afbe5f_allow_to_set_float_values_for_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9010a64e5a2d_add_indices.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9060dd4f6116_notification_uc.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/925771029620_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/92bdfe07e5f5_discord_integration.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/956659992419_add_columns_for_person_to_store_fido_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/96c79d31e648_add_mail_otp_columns_for_person.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/96f58a4a2a58_person_partial_index_for_email_only_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/98c90621cf58_add_for_client_flag_to_playlist.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/99825b9cc778_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9a09467f9b2c_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9bd17364fc18_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9e5b3a9b0cee_add_new_column_metadatadescriptor_data_type_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/9f8445f9b42c_add_man_days_fields.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a23682ccc1f1_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a519c710877c_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a65bdadbae2f_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a66508788c53_add_nb_assets_ready.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a6c25eed3ea1_add_login_failed_attemps_and_last_login_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/a7c43f3fbc76_add_duration_column_to_the_preview_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/aa0a60033106_feedback_request.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/addbad59c706_allow_to_manage_drawings_instead_of_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/addbbefa7028_add_departments_link_to_metadata_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/ae0127f2fc56_add_previewfile_width_and_previewfile_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/af1790868e2c_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b45cb782bb9c_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b4dd0add5f79_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b8c0a0f9d054_drop_task_status_is_reviewable.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b8ed0fb263f8_add_person_jti_and_jti_expiration_date.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/b97a71306fc8_add_is_casting_standby_column_to_entity.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/bf1347acdee2_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/c49e41f1298b_add_previewbackground.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/c68c2a62cfac_add_mimetype_column_to_attachment.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/c726b98be194_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/c81f3e83bdb5_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/cf3d365de164_add_entity_version_model.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/cf6cec6d6bf5_add_status_field_to_preview_file.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/d25118cddcaa_modify_salary_scale_model.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/d80267806131_task_status_new_column_is_default.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/d8dcd5196d57_add_casting_label.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/de8a3de227ef_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/deeacd38d373_for_projecttaskstatuslink_set_default_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/df1834485f57_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/df9f8a147e80_change_file_size_to_big_integer.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e1ef93f40d3d_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e29638428dfd_add_schedule_item_table.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e3f6db74cc1e_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e7e633bd6fa2_add_exceptions_to_budget_entries.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/e839d6603c09_add_person_id_to_shot_history.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/ee2373fbe3a4_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f0567e8d0c62_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f0c6cbb61869_add_production_style_field.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f4ff5a73d283_add_person_ldap_uid.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f5b113876a49_add_preferred_two_factor_authentication_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f5bdca075cdc_add_preview_download_flag_to_projects.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f874ad5e898a_add_link_entity_type_task_type.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/f995b28fb749_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fb6b6f188497_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fb87feaaa094_add_missing_unique_constraints.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fba149993140_add_missing_index.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fc322f908695_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/fee7c696166e_.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/feffd3c5b806_introduce_concepts.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/migrations/versions/ffeed4956ab1_add_more_details_to_projects.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/plugin_template/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/remote/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/remote/config_payload.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/remote/normalize_movie.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/remote/playlist.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/utils/__init__.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou/utils/movie.py +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou.egg-info/dependency_links.txt +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou.egg-info/entry_points.txt +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou.egg-info/not-zip-safe +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou.egg-info/requires.txt +0 -0
- {zou-0.20.44 → zou-0.20.46}/zou.egg-info/top_level.txt +0 -0
|
@@ -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
|
|
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
|
|
|
@@ -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(
|
|
26
|
+
def validate_email(
|
|
27
|
+
email, check_deliverability=config.MAIL_CHECK_DELIVERABILITY
|
|
28
|
+
):
|
|
27
29
|
try:
|
|
28
|
-
return email_validator.validate_email(
|
|
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
|