def get_task_types_schedule_items(project_id): """ Return all schedule items for given project. If no schedule item exists for a given task type, it creates one. """ task_types = tasks_service.get_task_types_for_project(project_id) task_type_map = base_service.get_model_map_from_array(task_types) schedule_items = set( ScheduleItem.query.filter_by(project_id=project_id).filter( ScheduleItem.object_id == None).all()) schedule_item_map = { str(schedule_item.task_type_id): schedule_item for schedule_item in schedule_items } new_schedule_items = set() schedule_item_to_remove = set() for schedule_item in schedule_items: if schedule_item.task_type_id is not None: if str(schedule_item.task_type_id) not in task_type_map: schedule_item_to_remove.add(schedule_item) for task_type in task_types: if task_type["id"] not in schedule_item_map: new_schedule_item = ScheduleItem.create( project_id=project_id, start_date=date.today(), end_date=date.today() + timedelta(days=1), task_type_id=task_type["id"], ) new_schedule_items.add(new_schedule_item) events.emit("schedule-item:new", {"schedule_item_id": str(new_schedule_item.id)}, project_id=project_id) schedule_items = (schedule_items.union(new_schedule_items) - schedule_item_to_remove) return sorted( [schedule_item.present() for schedule_item in schedule_items], key=lambda x: x["start_date"], )
def create_notifications_for_task_and_comment(task, comment, change=False): """ For given task and comment, create a notification for every assignee to the task and to every person participating to this task. """ task = tasks_service.get_task(task["id"]) recipient_ids = get_notification_recipients(task) recipient_ids.remove(comment["person_id"]) for recipient_id in recipient_ids: try: notification = create_notification( recipient_id, comment_id=comment["id"], author_id=comment["person_id"], task_id=comment["object_id"], read=False, change=change, type="comment" ) events.emit("notification:new", { "notification_id": notification["id"], "person_id": recipient_id }, persist=False) except PersonNotFoundException: pass for recipient_id in comment["mentions"]: notification = create_notification( recipient_id, comment_id=comment["id"], author_id=comment["person_id"], task_id=comment["object_id"], type="mention" ) events.emit("notification:new", { "notification_id": notification["id"], "person_id": recipient_id }, persist=False) return recipient_ids
def remove_task(task_id, force=False): """ Remove given task. Force deletion if the task has some comments and files related. This will lead to the deletion of all of them. """ task = Task.get(task_id) entity = Entity.get(task.entity_id) if force: working_files = WorkingFile.query.filter_by(task_id=task_id) for working_file in working_files: output_files = OutputFile.query.filter_by( source_file_id=working_file.id) for output_file in output_files: output_file.delete() working_file.delete() comments = Comment.query.filter_by(object_id=task_id) for comment in comments: notifications = Notification.query.filter_by(comment_id=comment.id) for notification in notifications: notification.delete() comment.delete() subscriptions = Subscription.query.filter_by(task_id=task_id) for subscription in subscriptions: subscription.delete() preview_files = PreviewFile.query.filter_by(task_id=task_id) for preview_file in preview_files: if entity.preview_file_id == preview_file.id: entity.update({"preview_file_id": None}) remove_preview_file(preview_file) time_spents = TimeSpent.query.filter_by(task_id=task_id) for time_spent in time_spents: time_spent.delete() task.delete() events.emit("task:delete", {"task_id": task_id}) return task.serialize()
def remove_person(person_id, force=True): person = Person.get(person_id) if force: for comment in Comment.get_all_by(person_id=person_id): remove_comment(comment.id) ApiEvent.delete_all_by(user_id=person_id) Notification.delete_all_by(person_id=person_id) SearchFilter.delete_all_by(person_id=person_id) DesktopLoginLog.delete_all_by(person_id=person_id) LoginLog.delete_all_by(person_id=person_id) Subscription.delete_all_by(person_id=person_id) TimeSpent.delete_all_by(person_id=person_id) for project in Project.query.filter(Project.team.contains(person)): project.team = [ member for member in project.team if str(member.id) != person_id ] project.save() for task in Task.query.filter(Task.assignees.contains(person)): task.assignees = [ assignee for assignee in task.assignees if str(assignee.id) != person_id ] task.save() for task in Task.get_all_by(assigner_id=person_id): task.update({"assigner_id": None}) for output_file in OutputFile.get_all_by(person_id=person_id): output_file.update({"person_id": None}) for working_file in WorkingFile.get_all_by(person_id=person_id): output_file.update({"person_id": None}) for task in WorkingFile.get_all_by(person_id=person_id): output_file.update({"person_id": None}) try: person.delete() events.emit("person:delete", {"person_id": person.id}) except IntegrityError: raise ModelWithRelationsDeletionException( "Some data are still linked to given person.") return person.serialize_safe()
def remove_metadata_descriptor(metadata_descriptor_id): """ Delete metadata descriptor and related informations. """ descriptor = get_metadata_descriptor_raw(metadata_descriptor_id) entities = Entity.get_all_by(project_id=descriptor.project_id) for entity in entities: metadata = fields.serialize_value(entity.data) if metadata is not None: metadata.pop(descriptor.field_name, None) entity.update({"data": metadata}) try: descriptor.delete() except ObjectDeletedError: pass events.emit( "metadata-descriptor:delete", {"metadata_descriptor_id": str(descriptor.id)}, ) clear_project_cache(str(descriptor.project_id)) return descriptor.serialize()
def create_shot(project_id, sequence_id, name, data={}): """ Create shot for given project and sequence. """ shot_type = get_shot_type() if sequence_id is not None: get_sequence(sequence_id) # raises SequenceNotFound if it fails. shot = Entity.get_by(entity_type_id=shot_type["id"], parent_id=sequence_id, project_id=project_id, name=name) if shot is None: shot = Entity.create(entity_type_id=shot_type["id"], project_id=project_id, parent_id=sequence_id, name=name, data=data) events.emit("shot:new", {"shot_id": shot.id}) return shot.serialize(obj_type="Shot")
def emit_app_preview_event(self, preview_file_id): """ Emit an event, each time a preview is added. """ preview_file = files_service.get_preview_file(preview_file_id) comment = tasks_service.get_comment_by_preview_file_id(preview_file_id) task = tasks_service.get_task(preview_file["task_id"]) comment_id = None if comment is not None: comment_id = comment["id"] events.emit("comment:update", {"comment_id": comment_id}, project_id=task["project_id"]) events.emit("preview-file:add-file", { "comment_id": comment_id, "task_id": preview_file["task_id"], "preview_file_id": preview_file["id"], "revision": preview_file["revision"], "extension": preview_file["extension"], "status": preview_file["status"], }, project_id=task["project_id"])
def assign_task(task_id, person_id): """ Assign given person to given task. Emit a *task:assign* event. """ task = get_task_raw(task_id) project_id = str(task.project_id) person = persons_service.get_person_raw(person_id) task.assignees.append(person) task.save() task_dict = task.serialize() events.emit( "task:assign", { "task_id": task.id, "person_id": person.id }, project_id=project_id, ) clear_task_cache(task_id) events.emit("task:update", {"task_id": task_id}, project_id=project_id) return task_dict
def update_metadata_descriptor(metadata_descriptor_id, changes): """ Update metadata descriptor information for given id. """ descriptor = get_metadata_descriptor_raw(metadata_descriptor_id) entities = Entity.get_all_by(project_id=descriptor.project_id) if "name" in changes and len(changes["name"]) > 0: changes["field_name"] = slugify.slugify(changes["name"]) for entity in entities: metadata = fields.serialize_value(entity.data) or {} value = metadata.pop(descriptor.field_name, None) if value is not None: metadata[changes["field_name"]] = value entity.update({"data": metadata}) descriptor.update(changes) events.emit( "metadata-descriptor:update", {"metadata_descriptor_id": str(descriptor.id)}, ) clear_project_cache(str(descriptor.project_id)) return descriptor.serialize()
def _finalize_task_creation(task_type, task_status, task): task_dict = task.serialize() task_dict["assignees"] = [] task_dict.update( { "task_status_id": task_status["id"], "task_status_name": task_status["name"], "task_status_short_name": task_status["short_name"], "task_status_color": task_status["color"], "task_type_id": task_type["id"], "task_type_name": task_type["name"], "task_type_color": task_type["color"], "task_type_priority": task_type["priority"], } ) events.emit( "task:new", {"task_id": task.id}, project_id=task_dict["project_id"] ) return task_dict
def get_or_create_status(name, short_name="", color="#f5f5f5", is_reviewable=False, is_done=False): """ Create a new task status if it doesn't exist. If it exists, it returns the status from database. """ task_status = TaskStatus.get_by(name=name) if task_status is None and len(short_name) > 0: task_status = TaskStatus.get_by(short_name=short_name) if task_status is None: task_status = TaskStatus.create(name=name, short_name=short_name or name.lower(), color=color, is_reviewable=is_reviewable, is_done=is_done) events.emit("task_status:new", {"task_status_id": task_status.id}) return task_status.serialize()
def clear_assignation(task_id): """ Clear task assignation and emit a *task:unassign* event. """ task = get_task_raw(task_id) project_id = str(task.project_id) assignees = [person.serialize() for person in task.assignees] task.update({"assignees": []}) clear_task_cache(task_id) task_dict = task.serialize() for assignee in assignees: events.emit( "task:unassign", { "person_id": assignee["id"], "task_id": task_id }, project_id=project_id, ) events.emit("task:update", {"task_id": task_id}, project_id=project_id) return task_dict
def remove_asset_link(asset_in_id, asset_out_id): """ Remove link asset together, unmark asset_in as asset out dependency. """ asset_in = get_asset_raw(asset_in_id) asset_out = get_asset_raw(asset_out_id) if asset_out in asset_in.entities_out: asset_in.entities_out = [ x for x in asset_in.entities_out if x.id != asset_out_id ] asset_in.save() events.emit( "asset:remove-link", { "asset_in": asset_in.id, "asset_out": asset_out.id }, project_id=str(asset_in.project_id), ) return asset_in.serialize(obj_type="Asset")
def update_casting(entity_id, casting): """ Update casting for given entity. Casting is an array of dictionaries made of two fields: `asset_id` and `nb_occurences`. """ entity = entities_service.get_entity_raw(entity_id) entity.update({"entities_out": []}) for cast in casting: if "asset_id" in cast and "nb_occurences" in cast: create_casting_link( entity.id, cast["asset_id"], nb_occurences=cast["nb_occurences"], label=cast.get("label", ""), ) entity_id = str(entity.id) if shots_service.is_shot(entity.serialize()): events.emit("shot:casting-update", {"shot_id": entity_id}) else: events.emit("asset:casting-update", {"asset_id": entity_id}) return casting
def delete(self, instance_id): parser = reqparse.RequestParser() parser.add_argument("force", default=False, type=bool) args = parser.parse_args() project = self.get_model_or_404(instance_id) project_dict = project.serialize() if projects_service.is_open(project_dict): return { "error": True, "message": "Only closed projects can be deleted", }, 400 else: self.check_delete_permissions(project_dict) if args["force"] is True: deletion_service.remove_project(instance_id) else: project.delete() events.emit("project:delete", {"project_id": project.id}) self.post_delete(project_dict) return "", 204
def remove_build_job(playlist, build_job_id): """ Remove build job from database and remove related temporary file from hard drive. """ job = BuildJob.get(build_job_id) movie_file_path = get_playlist_movie_file_path(playlist, job.serialize()) if os.path.exists(movie_file_path): os.remove(movie_file_path) try: file_store.remove_movie("playlists", build_job_id) except: current_app.logger.error("Playlist file can't be deleted: %s" % build_job_id) job.delete() events.emit("build-job:delete", { "build_job_id": build_job_id, "playlist_id": playlist["id"] }, project_id=playlist["project_id"]) return movie_file_path
def delete_reply(comment_id, reply_id): comment = tasks_service.get_comment_raw(comment_id) task = tasks_service.get_task(comment.object_id) if comment.replies is None: comment.replies = [] comment.replies = [ reply for reply in comment.replies if reply["id"] != reply_id ] comment.save() Notification.delete_all_by(reply_id=reply_id) events.emit( "comment:delete-reply", { "task_id": task["id"], "comment_id": str(comment.id), "reply_id": reply_id, }, project_id=task["project_id"], persist=False, ) return comment.serialize()
def put(self, instance_id): """ Update a model with data given in the request body. JSON format is expected. Model performs the validation automatically when fields are modified. """ try: data = self.get_arguments() instance = self.get_model_or_404(instance_id) self.check_update_permissions(instance.serialize(), data) data = self.update_data(data, instance_id) instance.update(data) events.emit( "%s:update" % self.model.__tablename__, {"%s_id" % self.model.__tablename__: instance.id} ) instance_dict = instance.serialize() self.post_update(instance_dict) return instance_dict, 200 except StatementError as exception: current_app.logger.error(str(exception)) return {"message": "Wrong id format"}, 400 except TypeError as exception: current_app.logger.error(str(exception)) return {"message": str(exception)}, 400 except IntegrityError as exception: current_app.logger.error(str(exception)) return {"message": str(exception)}, 400 except StatementError as exception: current_app.logger.error(str(exception)) return {"message": str(exception)}, 400 except ArgumentsException as exception: current_app.logger.error(str(exception)) return {"message": str(exception)}, 400
def create_task(task_type, entity, name="main"): """ Create a new task for given task type and entity. """ task_status = get_todo_status() try: try: current_user_id = persons_service.get_current_user()["id"] except RuntimeError: current_user_id = None task = Task.create(name=name, duration=0, estimation=0, completion_rate=0, start_date=None, end_date=None, due_date=None, real_start_date=None, project_id=entity["project_id"], task_type_id=task_type["id"], task_status_id=task_status["id"], entity_id=entity["id"], assigner_id=current_user_id, assignees=[]) task_dict = task.serialize() task_dict.update({ "task_status_id": task_status["id"], "task_status_name": task_status["name"], "task_status_short_name": task_status["short_name"], "task_status_color": task_status["color"], "task_type_id": task_type["id"], "task_type_name": task_type["name"], "task_type_color": task_type["color"], "task_type_priority": task_type["priority"] }) events.emit("task:new", {"task_id": task.id}) return task_dict except IntegrityError: pass # Tasks already exists, no need to create it.
def create_shot(project_id, sequence_id, name, data={}, nb_frames=0, description=None): """ Create shot for given project and sequence. """ shot_type = get_shot_type() if sequence_id is not None: # raises SequenceNotFound if it fails. sequence = get_sequence(sequence_id) shot = Entity.get_by( entity_type_id=shot_type["id"], parent_id=sequence_id, project_id=project_id, name=name, ) if shot is None: shot = Entity.create( entity_type_id=shot_type["id"], project_id=project_id, parent_id=sequence_id, name=name, data=data, nb_frames=nb_frames, description=description, ) events.emit( "shot:new", { "shot_id": shot.id, "episode_id": sequence["parent_id"], }, project_id=project_id, ) return shot.serialize(obj_type="Shot")
def test_register_all(self): event_map = { "task:start": self, "task:new": self } events.register_all(event_map) events.emit("task:start") self.assertEqual(self.counter, 2) events.emit("task:stop") self.assertEqual(self.counter, 2) events.emit("task:start") self.assertEqual(self.counter, 3) events.emit("task:new") self.assertEqual(self.counter, 4)
def get_entity_schedule_items(project_id, task_type_id, to_create, to_create_map, existing_schedule_items): schedule_item_map = { str(schedule_item.object_id): schedule_item for schedule_item in existing_schedule_items } new_schedule_items = set() schedule_item_to_remove = set() for schedule_item in existing_schedule_items: if schedule_item.object_id is not None: if str(schedule_item.object_id) not in to_create_map: schedule_item_to_remove.add(schedule_item) for entity in to_create: if entity["id"] not in schedule_item_map: new_schedule_item = ScheduleItem.create( project_id=project_id, start_date=date.today(), end_date=date.today() + timedelta(days=1), object_id=entity["id"], task_type_id=task_type_id, ) events.emit( "schedule-item:new", {"schedule_item_id": str(new_schedule_item.id)}, project_id=project_id, ) new_schedule_items.add(new_schedule_item) schedule_items = (existing_schedule_items.union(new_schedule_items) - schedule_item_to_remove) results = [] for schedule_item in schedule_items: result = schedule_item.present() result["name"] = to_create_map[result["object_id"]]["name"] results.append(result) return sorted(results, key=lambda x: x["name"])
def test_register(self): events.register("task:start", "inc_counter", self) events.emit("task:start") self.assertEqual(self.counter, 2) events.emit("task:stop") self.assertEqual(self.counter, 2) events.emit("task:start") self.assertEqual(self.counter, 3) event_models = events_service.get_last_events() self.assertEquals(len(event_models), 3) events.emit("task:start", persist=False) event_models = events_service.get_last_events() self.assertEquals(len(event_models), 3)
def create_person( email, password, first_name, last_name, phone="", role="user", desktop_login="", departments=[], ): """ Create a new person entry in the database. No operation are performed on password, so encrypted password is expected. """ if email is not None: email = email.strip() if not departments: departments = [] try: departments_objects = [ Department.get(department_id) for department_id in departments if department_id is not None ] except StatementError: raise DepartmentNotFoundException() person = Person.create( email=email, password=password, first_name=first_name, last_name=last_name, phone=phone, role=role, desktop_login=desktop_login, departments=departments_objects, ) events.emit("person:new", {"person_id": person.id}) clear_person_cache() return person.serialize(relations=True)
def create_notifications_for_task_and_comment(task, comment, change=False): """ For given task and comment, create a notification for every assignee to the task and to every person participating to this task. """ task = tasks_service.get_task(task["id"]) recipient_ids = get_notification_recipients(task) recipient_ids.remove(comment["person_id"]) for recipient_id in recipient_ids: try: person = persons_service.get_person(recipient_id) notification = create_notification( person, comment, read=False, change=change ) events.emit("notifications:new", { "id": notification["id"], "person_id": recipient_id }) except PersonNotFoundException: pass return recipient_ids
def remove_project(project_id): from zou.app.services import playlists_service tasks = Task.query.filter_by(project_id=project_id) for task in tasks: remove_task(task.id, force=True) query = EntityLink.query.join(Entity, EntityLink.entity_in_id == Entity.id).filter( Entity.project_id == project_id) for link in query: link.delete_no_commit() EntityLink.commit() query = EntityVersion.query.join( Entity, EntityVersion.entity_id == Entity.id).filter( Entity.project_id == project_id) for version in query: version.delete_no_commit() EntityLink.commit() playlists = Playlist.query.filter_by(project_id=project_id) for playlist in playlists: playlists_service.remove_playlist(playlist.id) ApiEvent.delete_all_by(project_id=project_id) Entity.delete_all_by(project_id=project_id) MetadataDescriptor.delete_all_by(project_id=project_id) Milestone.delete_all_by(project_id=project_id) ScheduleItem.delete_all_by(project_id=project_id) SearchFilter.delete_all_by(project_id=project_id) for news in News.query.join(Task).filter_by(project_id=project_id).all(): news.delete_no_commit() News.commit() project = Project.get(project_id) project.delete() events.emit("project:delete", {"project_id": project.id}) return project_id
def new_comment(task_id, task_status_id, person_id, text, object_type="Task", files={}, checklist=[], created_at=""): """ Create a new comment for given object (by default, it considers this object as a Task). """ created_at_date = None task = tasks_service.get_task(task_id) if created_at is not None and len(created_at) > 0: try: created_at_date = fields.get_date_object( created_at, date_format="%Y-%m-%d %H:%M:%S") except ValueError: pass comment = Comment.create(object_id=task_id, object_type=object_type, task_status_id=task_status_id, person_id=person_id, mentions=get_comment_mentions(task_id, text), checklist=checklist, text=text, created_at=created_at_date) comment = comment.serialize(relations=True) comment["attachment_files"] = [] for uploaded_file in files.values(): attachment_file = create_attachment(comment, uploaded_file) comment["attachment_files"].append(attachment_file) events.emit("comment:new", {"comment_id": comment["id"]}, project_id=task["project_id"]) return comment
def start_task(task): wip_status = get_wip_status() task_is_not_already_wip = \ task.task_status_id is None \ or task.task_status_id != wip_status.id if task_is_not_already_wip: task_dict_before = task.serialize() new_data = {"task_status_id": wip_status.id} if task.real_start_date is None: new_data["real_start_date"] = datetime.datetime.now() task.update(new_data) task_dict_after = task.serialize() events.emit("task:start", { "task_before": task_dict_before, "task_after": task_dict_after }) return task
def add_preview_file_to_comment(comment_id, person_id, task_id, revision=0): """ Add a preview to comment preview list. Auto set the revision field (add 1 if it's a new preview, keep the preview revision in other cases). """ comment = get_comment_raw(comment_id) news = News.get_by(comment_id=comment_id) if revision == 0 and len(comment.previews) == 0: revision = get_next_preview_revision(task_id) elif revision == 0: revision = comment.previews[0].revision preview_file = files_service.create_preview_file_raw( str(uuid.uuid4())[:13], revision, task_id, person_id ) events.emit("preview-file:create", {"preview_file_id": preview_file.id}) comment.previews.append(preview_file) comment.save() if news is not None: news.update({"preview_file_id": preview_file.id}) events.emit("comment:update", {"comment_id": comment.id}) return preview_file.serialize()
def create_comment( object_id, task_status_id, person_id, text, object_type="Task" ): """ Create a new comment for given object (by default, it considers this object as a Task). """ comment = Comment.create( object_id=object_id, object_type=object_type, task_status_id=task_status_id, person_id=person_id, text=text ) events.emit("comment:new", { "comment_id": comment.id, }) return comment.serialize()