From 8f5de366e6c6d1cf4a0ad3fabebe6e56fb0f7967 Mon Sep 17 00:00:00 2001 From: Frank Rousseau Date: Tue, 20 Aug 2024 17:27:12 +0200 Subject: [PATCH 1/6] [cli] Command to check if the db was initialized --- zou/app/utils/dbhelpers.py | 14 +++++++++++++- zou/cli.py | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/zou/app/utils/dbhelpers.py b/zou/app/utils/dbhelpers.py index 8d32ee7ef1..c5837f4e7f 100644 --- a/zou/app/utils/dbhelpers.py +++ b/zou/app/utils/dbhelpers.py @@ -1,4 +1,4 @@ -from sqlalchemy import create_engine +from sqlalchemy import create_engine, inspect from sqlalchemy_utils import database_exists, create_database from sqlalchemy.engine.url import URL from sqlalchemy.orm import close_all_sessions @@ -39,3 +39,15 @@ def drop_all(): db.session.flush() close_all_sessions() return db.drop_all() + + +def is_init(): + """ + Check if database is initialized. + """ + from zou.app import db + from zou.app.models.project_status import ProjectStatus + return ( + inspect(db.engine).has_table("person") + and db.session.query(ProjectStatus).count() == 2 + ) diff --git a/zou/cli.py b/zou/cli.py index 4fdbfe3d5f..8dc7f17491 100755 --- a/zou/cli.py +++ b/zou/cli.py @@ -47,6 +47,21 @@ def init_db(): print("Database and tables created.") +@cli.command() +def is_db_ready(): + """ + Return a message telling if the database wheter the database is + initiliazed or not." + """ + with app.app_context(): + is_init = dbhelpers.is_init() + if is_init: + print("Database is initiliazed.") + else: + print("Database is not initiliazed. " + "Run 'zou init-db' and 'init-data'.") + + @cli.command() @click.option("--message", default="") def migrate_db(message): From 32c66fc5ee5bd6477396c07e315385661be875fb Mon Sep 17 00:00:00 2001 From: Frank Rousseau Date: Tue, 20 Aug 2024 17:51:19 +0200 Subject: [PATCH 2/6] [tasks] Modify project tasks route behaviour * Allow to filter by episode * Widen permissions to allow read access to non admin users --- zou/app/blueprints/tasks/resources.py | 27 +++++++++++++++++++++++++-- zou/app/services/tasks_service.py | 27 ++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/zou/app/blueprints/tasks/resources.py b/zou/app/blueprints/tasks/resources.py index b81f9e9baf..de0c1b5d9b 100644 --- a/zou/app/blueprints/tasks/resources.py +++ b/zou/app/blueprints/tasks/resources.py @@ -1463,7 +1463,6 @@ class ProjectTasksResource(Resource, ArgsMixin): """ @jwt_required() - @permissions.require_admin def get(self, project_id): """ Retrieve all tasks related to given project. @@ -1478,13 +1477,37 @@ def get(self, project_id): type: string format: UUID x-example: a24a6ea4-ce75-4665-a070-57453082c25 + - in: query + name: page + required: False + type: integer + x-example: 1 + - in: query + name: task_type_id + required: False + type: string + format: UUID + x-example: a24a6ea4-ce75-4665-a070-57453082c25 + - in: query + name: episode_id + required: False + type: string + format: UUID + x-example: a24a6ea4-ce75-4665-a070-57453082c25 responses: 200: description: All tasks related to given project """ projects_service.get_project(project_id) page = self.get_page() - return tasks_service.get_tasks_for_project(project_id, page) + task_type_id = self.get_task_type_id() + episode_id = self.get_episode_id() + return tasks_service.get_tasks_for_project( + project_id, + page, + task_type_id=task_type_id, + episode_id=episode_id + ) class ProjectCommentsResource(Resource, ArgsMixin): diff --git a/zou/app/services/tasks_service.py b/zou/app/services/tasks_service.py index a4e8004c9f..83e88f5d9f 100644 --- a/zou/app/services/tasks_service.py +++ b/zou/app/services/tasks_service.py @@ -1698,13 +1698,38 @@ def get_time_spents_for_project(project_id, page=0): return query_utils.get_paginated_results(query, page) -def get_tasks_for_project(project_id, page=0): +def get_tasks_for_project( + project_id, + page=0, + task_type_id=None, + episode_id=None +): """ Return all tasks for given project. """ query = Task.query.filter(Task.project_id == project_id).order_by( Task.updated_at.desc() ) + if task_type_id is not None: + query = query.filter(Task.task_type_id == task_type_id) + if episode_id is not None: + Sequence = aliased(Entity, name="sequence") + query = ( + query + .join(Entity, Entity.id == Task.entity_id) + .join(Sequence, Sequence.id == Entity.parent_id) + .filter(Sequence.parent_id == episode_id) + ) + + if permissions.has_vendor_permissions(): + query = query.filter(user_service.build_assignee_filter()) + elif not permissions.has_admin_permissions(): + query = query.join(Project).filter( + user_service.build_related_projects_filter() + ) + return query + + return query_utils.get_paginated_results(query, page, relations=True) From d4acb7587a4cd91f42114494df87efd877843216 Mon Sep 17 00:00:00 2001 From: Frank Rousseau Date: Tue, 20 Aug 2024 17:52:53 +0200 Subject: [PATCH 3/6] [previews] Add tvpain extension to allowed files --- zou/app/blueprints/previews/resources.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zou/app/blueprints/previews/resources.py b/zou/app/blueprints/previews/resources.py index 0bb1738205..d8a525f3dd 100644 --- a/zou/app/blueprints/previews/resources.py +++ b/zou/app/blueprints/previews/resources.py @@ -76,6 +76,7 @@ "sbbkp", "svg", "swf", + "tvpp", "wav", "zip", ] From c0811f8984d64d9ea08fccbfcea450b3231738cb Mon Sep 17 00:00:00 2001 From: Frank Rousseau Date: Tue, 20 Aug 2024 17:53:32 +0200 Subject: [PATCH 4/6] [shots] Allow to update shots via `shots/shot_id` route --- zou/app/blueprints/shots/resources.py | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/zou/app/blueprints/shots/resources.py b/zou/app/blueprints/shots/resources.py index d130aea686..e55ee4c236 100644 --- a/zou/app/blueprints/shots/resources.py +++ b/zou/app/blueprints/shots/resources.py @@ -48,6 +48,52 @@ def get(self, shot_id): user_service.check_entity_access(shot["id"]) return shot + @jwt_required() + def put(self, shot_id): + """ + Update given shot. + --- + tags: + - Shots + parameters: + - in: path + name: shot_id + required: True + type: string + format: UUID + x-example: a24a6ea4-ce75-4665-a070-57453082c25 + - in: body + name: data + required: True + type: object + responses: + 200: + description: Update given shot + """ + shot = shots_service.get_shot(shot_id) + user_service.check_manager_project_access(shot["project_id"]) + data = request.json + if data is None: + raise ArgumentsException( + "Data are empty. Please verify that you sent JSON data and" + " that you set the right headers." + ) + for field in [ + "id", + "created_at", + "updated_at", + "instance_casting", + "project_id", + "entities_in", + "entities_out", + "type", + "shotgun_id", + "created_by" + ]: + data.pop(field, None) + + return shots_service.update_shot(shot_id, data) + @jwt_required() def delete(self, shot_id): """ From 07f6c23025583da7b673a189c3720f0ed929a696 Mon Sep 17 00:00:00 2001 From: Frank Rousseau Date: Tue, 20 Aug 2024 17:59:01 +0200 Subject: [PATCH 5/6] [entities] Widen read access rights to non admin users --- zou/app/blueprints/crud/entity.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/zou/app/blueprints/crud/entity.py b/zou/app/blueprints/crud/entity.py index b420049893..464633d537 100644 --- a/zou/app/blueprints/crud/entity.py +++ b/zou/app/blueprints/crud/entity.py @@ -11,6 +11,7 @@ EntityLink, EntityConceptLink, ) +from zou.app.models.project import Project from zou.app.models.subscription import Subscription from zou.app.services import ( assets_service, @@ -23,7 +24,7 @@ user_service, concepts_service, ) -from zou.app.utils import events, date_helpers +from zou.app.utils import date_helpers, events, permissions from werkzeug.exceptions import NotFound @@ -56,6 +57,16 @@ def check_create_permissions(self, entity): def emit_create_event(self, entity_dict): self.emit_event("new", entity_dict) + def check_read_permissions(self): + return not permissions.has_vendor_permissions() + + def add_project_permission_filter(self, query): + if not permissions.has_admin_permissions(): + query = query.join(Project).filter( + user_service.build_related_projects_filter() + ) + return query + def update_data(self, data): data = super().update_data(data) data["created_by"] = persons_service.get_current_user()["id"] From 0c250384c4854bef547d2a0f15bb74052e8145c5 Mon Sep 17 00:00:00 2001 From: Frank Rousseau Date: Tue, 20 Aug 2024 17:59:33 +0200 Subject: [PATCH 6/6] [tasks] Widen raw tasks route read-access to non admin users --- zou/app/blueprints/crud/task.py | 45 ++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/zou/app/blueprints/crud/task.py b/zou/app/blueprints/crud/task.py index 59db9b53f3..41780f965d 100644 --- a/zou/app/blueprints/crud/task.py +++ b/zou/app/blueprints/crud/task.py @@ -1,8 +1,11 @@ from flask import request, current_app from flask_jwt_extended import jwt_required +from sqlalchemy.orm import aliased from sqlalchemy.exc import IntegrityError + from zou.app.mixin import ArgsMixin +from zou.app.models.entity import Entity from zou.app.models.person import Person from zou.app.models.project import Project from zou.app.models.task import Task @@ -21,7 +24,7 @@ from zou.app.blueprints.crud.base import BaseModelsResource, BaseModelResource -class TasksResource(BaseModelsResource): +class TasksResource(BaseModelsResource, ArgsMixin): def __init__(self): BaseModelsResource.__init__(self, Task) @@ -37,6 +40,46 @@ def add_project_permission_filter(self, query): ) return query + def build_filters(self, options): + ( + many_join_filter, + in_filter, + name_filter, + criterions, + ) = super().build_filters(options) + if "project_id" in criterions: + del criterions["project_id"] + if "episode_id" in criterions: + del criterions["episode_id"] + return ( + many_join_filter, + in_filter, + name_filter, + criterions, + ) + + def apply_filters(self, query, options): + query = super().apply_filters(query, options) + + project_id = options.get("project_id", None) + episode_id = options.get("episode_id", None) + if episode_id is not None: + Sequence = aliased(Entity) + query = ( + query + .join(Entity, Task.entity_id == Entity.id) + .join(Sequence, Entity.parent_id == Sequence.id) + .filter(Sequence.parent_id == episode_id) + ) + elif project_id is not None: + query = ( + query + .join(Entity, Task.entity_id == Entity.id) + .filter(Entity.project_id == project_id) + ) + + return query + def post(self): """ Create a task with data given in the request body. JSON format is