class WorkingFile(db.Model, BaseMixin, SerializerMixin): """ Describes the file related to the work done on a given task. It is used as source of output files published for a given entity. """ shotgun_id = db.Column(db.Integer(), index=True) name = db.Column(db.String(250)) description = db.Column(db.String(200)) comment = db.Column(db.Text()) revision = db.Column(db.Integer()) size = db.Column(db.Integer()) checksum = db.Column(db.Integer()) path = db.Column(db.String(400)) task_id = db.Column(UUIDType(binary=False), db.ForeignKey("task.id"), index=True) entity_id = db.Column(UUIDType(binary=False), db.ForeignKey("entity.id"), index=True) person_id = \ db.Column(UUIDType(binary=False), db.ForeignKey("person.id")) software_id = \ db.Column(UUIDType(binary=False), db.ForeignKey("software.id")) outputs = relationship("OutputFile", back_populates="source_file") __table_args__ = (db.UniqueConstraint("name", "task_id", "entity_id", "revision", name="working_file_uc"), ) def __repr__(self): return "<WorkingFile %s>" % self.id
class WorkingFile(db.Model, BaseMixin, SerializerMixin): shotgun_id = db.Column(db.Integer()) name = db.Column(db.String(250)) description = db.Column(db.String(200)) comment = db.Column(db.Text()) revision = db.Column(db.Integer()) size = db.Column(db.Integer()) checksum = db.Column(db.Integer()) task_id = db.Column(UUIDType(binary=False), db.ForeignKey("task.id")) entity_id = db.Column(UUIDType(binary=False), db.ForeignKey("entity.id")) person_id = \ db.Column(UUIDType(binary=False), db.ForeignKey("person.id")) __table_args__ = ( db.UniqueConstraint( "name", "task_id", "entity_id", "revision", name="working_file_uc" ), ) def __repr__(self): return "<WorkingFile %s>" % self.id
class Playlist(db.Model, BaseMixin, SerializerMixin): """ Describes a playlist. The goal is to review a set of shipped materials. """ name = db.Column(db.String(80), nullable=False) shots = db.Column(JSONB) project_id = db.Column(UUIDType(binary=False), db.ForeignKey("project.id"), index=True) episode_id = db.Column(UUIDType(binary=False), db.ForeignKey("entity.id"), index=True) for_client = db.Column(db.Boolean(), default=False, index=True) for_entity = db.Column(db.String(10), default="shot", index=True) is_for_all = db.Column(db.Boolean, default=False) build_jobs = relationship("BuildJob") __table_args__ = (db.UniqueConstraint("name", "project_id", "episode_id", name="playlist_uc"), ) @classmethod def create_from_import(cls, data): del data["type"] del data["build_jobs"] previous_data = cls.get(data["id"]) if previous_data is None: return cls.create(**data) else: previous_data.update(data) return previous_data
class OutputType(db.Model, BaseMixin, SerializerMixin): """ Type of an output files (geometry, cache, etc.) """ name = db.Column(db.String(40), unique=True, nullable=False, index=True) short_name = db.Column(db.String(20), nullable=False)
class TaskType(db.Model, BaseMixin, SerializerMixin): """ Categorize tasks in domain areas: modeling, animation, etc. """ name = db.Column(db.String(40), nullable=False) short_name = db.Column(db.String(20)) color = db.Column(db.String(7), default="#FFFFFF") priority = db.Column(db.Integer, default=1) for_shots = db.Column(db.Boolean, default=False) for_entity = db.Column(db.String(30), default="Asset") allow_timelog = db.Column(db.Boolean, default=True) shotgun_id = db.Column(db.Integer, index=True) department_id = db.Column( UUIDType(binary=False), db.ForeignKey("department.id") ) __table_args__ = ( db.UniqueConstraint( 'name', 'for_entity', 'department_id', name='task_type_uc' ), )
class FileStatus(db.Model, BaseMixin, SerializerMixin): """ Describe the state of a given file. """ name = db.Column(db.String(40), unique=True, nullable=False) color = db.Column(db.String(7), nullable=False)
class DependentFile(db.Model, BaseMixin, SerializerMixin): """ Describe a file generated from a CG artist scene. It aims to know the dependencies of an outputfile. """ __tablename__ = "dependent_file" source_output_file_id = db.Column( UUIDType(binary=False), db.ForeignKey("output_file.id"), nullable=True ) size = db.Column(db.Integer()) checksum = db.Column(db.String(32)) extension = db.Column(db.String(10)) path = db.Column(db.String(400), unique=True) used_by = relationship( "OutputFile", secondary=dependent_table, back_populates="dependent_files" ) project_id = db.Column( UUIDType(binary=False), db.ForeignKey("project.id") ) temporal_entity_id = db.Column( UUIDType(binary=False), db.ForeignKey("entity.id"), default=None, nullable=True, ) def __repr__(self): return "<DependentFile %s>" % self.id
class Task(db.Model, BaseMixin, SerializerMixin): """ Describes a task done by a CG artist on an entity of the CG production. The task has a state and assigned to people. It handles notion of time like duration, start date and end date. """ name = db.Column(db.String(80), nullable=False) description = db.Column(db.String(200)) priority = db.Column(db.Integer, default=0) duration = db.Column(db.Integer, default=0) estimation = db.Column(db.Integer, default=0) completion_rate = db.Column(db.Integer, default=0) sort_order = db.Column(db.Integer, default=0) start_date = db.Column(db.DateTime) end_date = db.Column(db.DateTime) due_date = db.Column(db.DateTime) real_start_date = db.Column(db.DateTime) shotgun_id = db.Column(db.Integer) project_id = db.Column( UUIDType(binary=False), db.ForeignKey('project.id'), index=True ) task_type_id = db.Column( UUIDType(binary=False), db.ForeignKey('task_type.id') ) task_status_id = db.Column( UUIDType(binary=False), db.ForeignKey('task_status.id') ) entity_id = db.Column( UUIDType(binary=False), db.ForeignKey('entity.id'), index=True ) assigner_id = db.Column( UUIDType(binary=False), db.ForeignKey('person.id') ) assignees = db.relationship( 'Person', secondary=association_table ) __table_args__ = ( db.UniqueConstraint( 'name', 'project_id', 'task_type_id', 'entity_id', name='task_uc' ), ) def assignees_as_string(self): return ", ".join([x.full_name() for x in self.assignees])
class Entity(db.Model, BaseMixin, SerializerMixin): id = db.Column( UUIDType(binary=False), primary_key=True, default=fields.gen_uuid ) name = db.Column(db.String(160), nullable=False) description = db.Column(db.String(300)) shotgun_id = db.Column(db.Integer) project_id = db.Column( UUIDType(binary=False), db.ForeignKey('project.id'), nullable=False) entity_type_id = db.Column( UUIDType(binary=False), db.ForeignKey('entity_type.id'), nullable=False) parent_id = db.Column( UUIDType(binary=False), db.ForeignKey('entity.id')) data = db.Column(JSONB) entities_out = db.relationship( 'Entity', secondary=entity_link, primaryjoin=(id == entity_link.c.entity_in_id), secondaryjoin=(id == entity_link.c.entity_out_id) ) __table_args__ = ( db.UniqueConstraint( 'name', 'project_id', 'entity_type_id', 'parent_id', name='entity_uc' ), )
class TimeSpentStatus(db.Model, BaseMixin, SerializerMixin): """ Describe the state of a given file. """ name = db.Column(db.String(40), unique=False, nullable=False) short_name = db.Column(db.String(40), unique=True, nullable=False)
class MetadataDescriptor(db.Model, BaseMixin, SerializerMixin): """ This models allow to identify which metadata are available for a given project and a given entity type. """ project_id = db.Column( UUIDType(binary=False), db.ForeignKey("project.id"), nullable=False, index=True, ) entity_type = db.Column(db.String(60), nullable=False, index=True) name = db.Column(db.String(120), nullable=False) field_name = db.Column(db.String(120), nullable=False) choices = db.Column(JSONB) for_client = db.Column(db.Boolean(), default=False, index=True) __table_args__ = (db.UniqueConstraint("project_id", "entity_type", "name", name="metadata_descriptor_uc"), ) def __repr__(self): return "<MetadataDescriptor %s>" % self.id
class Organisation(db.Model, BaseMixin, SerializerMixin): """ Model to represent current organisation settings. """ name = db.Column(db.String(80), unique=True, nullable=False) hours_by_day = db.Column(db.Integer(), default=8, nullable=False) has_avatar = db.Column(db.Boolean(), default=False) use_original_file_name = db.Column(db.Boolean(), default=False) timesheets_locked = db.Column(db.Boolean(), default=False) hd_by_default = db.Column(db.Boolean(), default=False) chat_token_slack = db.Column(db.String(80), default="") def present(self): return fields.serialize_dict( { "id": self.id, "chat_token_slack": self.chat_token_slack, "name": self.name, "has_avatar": self.has_avatar, "hours_by_day": self.hours_by_day, "hd_by_default": self.hd_by_default, "use_original_file_name": self.use_original_file_name, "timesheets_locked": self.timesheets_locked, } )
class PreviewFile(db.Model, BaseMixin, SerializerMixin): """ Describes a file which is aimed at being reviewed. It is not a publication neither a working file. """ name = db.Column(db.String(250)) revision = db.Column(db.Integer(), default=1) description = db.Column(db.Text()) source = db.Column(db.String(40)) shotgun_id = db.Column(db.Integer, unique=True) is_movie = db.Column(db.Boolean, default=False) url = db.Column(db.String(600)) uploaded_movie_url = db.Column(db.String(600)) uploaded_movie_name = db.Column(db.String(150)) task_id = db.Column(UUIDType(binary=False), db.ForeignKey("task.id")) person_id = db.Column(UUIDType(binary=False), db.ForeignKey("person.id")) source_file_id = db.Column(UUIDType(binary=False), db.ForeignKey("output_file.id")) __table_args__ = (db.UniqueConstraint("name", "task_id", "revision", name="preview_uc"), ) def __repr__(self): return "<PreviewFile %s>" % self.id
class AttachmentFile(db.Model, BaseMixin, SerializerMixin): """ Describes a file which is attached to a comment. """ name = db.Column(db.String(250)) size = db.Column(db.Integer(), default=1) extension = db.Column(db.String(6)) mimetype = db.Column(db.String(255)) comment_id = db.Column(UUIDType(binary=False), db.ForeignKey("comment.id"), index=True) __table_args__ = (db.UniqueConstraint("name", "comment_id", name="attachment_uc"), ) def __repr__(self): return "<AttachmentFile %s>" % self.id def present(self): return { "id": str(self.id), "name": self.name, "extension": self.extension, "size": self.size, }
class Task(db.Model, BaseMixin, SerializerMixin): name = db.Column(db.String(80), nullable=False) description = db.Column(db.String(200)) duration = db.Column(db.Integer) estimation = db.Column(db.Integer) completion_rate = db.Column(db.Integer) sort_order = db.Column(db.Integer) start_date = db.Column(db.DateTime) end_date = db.Column(db.DateTime) due_date = db.Column(db.DateTime) real_start_date = db.Column(db.DateTime) shotgun_id = db.Column(db.Integer) project_id = \ db.Column(UUIDType(binary=False), db.ForeignKey('project.id')) task_type_id = \ db.Column(UUIDType(binary=False), db.ForeignKey('task_type.id')) task_status_id = \ db.Column(UUIDType(binary=False), db.ForeignKey('task_status.id')) entity_id = \ db.Column(UUIDType(binary=False), db.ForeignKey('entity.id')) assigner_id = \ db.Column(UUIDType(binary=False), db.ForeignKey('person.id')) assignees = db.relationship('Person', secondary=association_table) def assignees_as_string(self): return ", ".join([x.full_name() for x in self.assignees])
class Person(db.Model, BaseMixin, SerializerMixin): first_name = db.Column(db.String(80), nullable=False) last_name = db.Column(db.String(80), nullable=False) email = db.Column(EmailType, unique=True) phone = db.Column(db.String(30)) active = db.Column(db.Boolean(), default=True) last_presence = db.Column(db.Date()) password = db.Column(db.Binary(60)) shotgun_id = db.Column(db.Integer, unique=True) timezone = db.Column(TimezoneType(backend="pytz"), default=pytz_timezone("Europe/Paris")) locale = db.Column(LocaleType, default=Locale("en", "US")) data = db.Column(JSONB) skills = db.relationship("Department", secondary=department_link) def __repr__(self): return "<Person %s>" % self.full_name() def full_name(self): if sys.version_info[0] < 3: return "%s %s" % (self.first_name.encode("utf-8"), self.last_name.encode("utf-8")) else: return "%s %s" % (self.first_name, self.last_name)
class Entity(db.Model, BaseMixin, SerializerMixin): """ Base model to represent assets, shots, sequences, episodes and scenes. They have different meaning but they share the same behaviour toward tasks and files. """ id = db.Column(UUIDType(binary=False), primary_key=True, default=fields.gen_uuid) name = db.Column(db.String(160), nullable=False) code = db.Column(db.String(160)) # To store sanitized version of name description = db.Column(db.String(1200)) shotgun_id = db.Column(db.Integer) canceled = db.Column(db.Boolean, default=False) nb_frames = db.Column(db.Integer) # Specific to shots project_id = db.Column(UUIDType(binary=False), db.ForeignKey("project.id"), nullable=False, index=True) entity_type_id = db.Column(UUIDType(binary=False), db.ForeignKey("entity_type.id"), nullable=False, index=True) parent_id = db.Column(UUIDType(binary=False), db.ForeignKey("entity.id"), index=True) # sequence or episode source_id = db.Column( UUIDType(binary=False), db.ForeignKey("entity.id"), index=True, nullable=True ) # if the entity is generated from another one (like shots from scene). preview_file_id = db.Column( UUIDType(binary=False), db.ForeignKey("preview_file.id", name="fk_main_preview")) data = db.Column(JSONB) entities_out = db.relationship( "Entity", secondary="entity_link", primaryjoin=(id == EntityLink.entity_in_id), secondaryjoin=(id == EntityLink.entity_out_id), backref="entities_in") instance_casting = db.relationship("AssetInstance", secondary="asset_instance_link", backref="shots") __table_args__ = (db.UniqueConstraint("name", "project_id", "entity_type_id", "parent_id", name="entity_uc"), )
class Project(db.Model, BaseMixin, SerializerMixin): name = db.Column(db.String(80), nullable=False, unique=True) description = db.Column(db.String(200)) shotgun_id = db.Column(db.Integer) file_tree = db.Column(JSONB) project_status_id = \ db.Column(UUIDType(binary=False), db.ForeignKey('project_status.id'))
class Software(db.Model, BaseMixin, SerializerMixin): """ Describes software used by working files. """ name = db.Column(db.String(40), unique=True, nullable=False) short_name = db.Column(db.String(20), nullable=False) file_extension = db.Column(db.String(20), nullable=False) secondary_extensions = db.Column(JSONB)
class CustomAction(db.Model, BaseMixin, SerializerMixin): """ Custom actions are HTTP links that can be activated outside of the API. They are mainly aimed at being used by the web frontend to allow studio to make custom HTTP calls. """ name = db.Column(db.String(80), nullable=False) url = db.Column(db.String(400)) entity_type = db.Column(db.String(40), default="all")
class TaskStatus(db.Model, BaseMixin, SerializerMixin): """ Describe the state of a task. A status marked as reviewable expects a preview file linked to relate comment. """ name = db.Column(db.String(40), nullable=False) short_name = db.Column(db.String(10), unique=True, nullable=False) color = db.Column(db.String(7), nullable=False) is_reviewable = db.Column(db.Boolean(), default=False) is_done = db.Column(db.Boolean(), default=False) shotgun_id = db.Column(db.Integer)
class SearchFilter(db.Model, BaseMixin, SerializerMixin): """ Filters allow to store quick search on a list: asset list, shot list, sequence list, todo-list... """ list_type = db.Column(db.String(80), nullable=False, index=True) entity_type = db.Column(db.String(80)) name = db.Column(db.String(200), nullable=False, default="") search_query = db.Column(db.String(200), nullable=False, default="") person_id = db.Column(UUIDType(binary=False), db.ForeignKey("person.id")) project_id = db.Column(UUIDType(binary=False), db.ForeignKey("project.id"))
class AssetInstance(db.Model, BaseMixin, SerializerMixin): """ An asset instance is the representation of an asset in a given shot or layout scene. It is useful for complex scenes where an asset needs extra treatments only related to the given shot or layout scene. An asset can have multiple instances in a scene or in a shot (ex: a sword in a battle field). """ asset_id = db.Column( UUIDType(binary=False), db.ForeignKey('entity.id'), nullable=False, index=True ) name = db.Column(db.String(80)) number = db.Column(db.Integer()) description = db.Column(db.String(200)) active = db.Column(db.Boolean(), default=True) data = db.Column(JSONB) scene_id = db.Column( UUIDType(binary=False), db.ForeignKey('entity.id'), index=True ) __table_args__ = ( db.UniqueConstraint( 'asset_id', 'scene_id', 'number', name='asset_instance_uc' ), db.UniqueConstraint( 'scene_id', 'name', name='asset_instance_name_uc' ) ) # Do not use these column. They are deprecated and will be dropped in # upcoming version entity_id = db.Column( UUIDType(binary=False), db.ForeignKey('entity.id'), ) entity_type_id = db.Column( UUIDType(binary=False), db.ForeignKey('entity_type.id') ) def __repr__(self): return "<AssetInstance %s>" % self.id
class Person(db.Model, BaseMixin, SerializerMixin): """ Describe a member of the studio (and an API user). """ first_name = db.Column(db.String(80), nullable=False) last_name = db.Column(db.String(80), nullable=False) email = db.Column(EmailType, unique=True) phone = db.Column(db.String(30)) active = db.Column(db.Boolean(), default=True) last_presence = db.Column(db.Date()) password = db.Column(db.Binary(60)) desktop_login = db.Column(db.String(80)) shotgun_id = db.Column(db.Integer, unique=True) timezone = db.Column( TimezoneType(backend="pytz"), default=pytz_timezone("Europe/Paris") ) locale = db.Column(LocaleType, default=Locale("en", "US")) data = db.Column(JSONB) role = db.Column(db.String(30), default="user") has_avatar = db.Column(db.Boolean(), default=False) skills = db.relationship( "Department", secondary=department_link ) def __repr__(self): if sys.version_info[0] < 3: return "<Person %s>" % self.full_name().encode("utf-8") else: return "<Person %s>" % self.full_name() def full_name(self): return "%s %s" % ( self.first_name, self.last_name ) def serialize(self, obj_type="Person"): data = SerializerMixin.serialize(self, "Person") data["full_name"] = self.full_name() return data def serialize_safe(self): data = SerializerMixin.serialize(self, "Person") data["full_name"] = self.full_name() del data["password"] return data
class TaskType(db.Model, BaseMixin, SerializerMixin): name = db.Column(db.String(40), nullable=False) color = db.Column(db.String(7), default="#FFFFFF") shotgun_id = db.Column(db.Integer) department_id = \ db.Column( UUIDType(binary=False), db.ForeignKey("department.id") ) __table_args__ = (db.UniqueConstraint('name', 'department_id', name='task_type_uc'), )
class Comment(db.Model, BaseMixin, SerializerMixin): """ Comment can occurs on any object but they are mainly used on tasks. In the case of comment tasks, they are linked to a task status and eventually on a preview file. The status means that comment leads to task status change. The preview file means that the comment relates to this preview in the context of the task. """ shotgun_id = db.Column(db.Integer) object_id = db.Column(UUIDType(binary=False), nullable=False, index=True) object_type = db.Column(db.String(80), nullable=False, index=True) text = db.Column(db.Text()) data = db.Column(JSONB) checklist = db.Column(JSONB) pinned = db.Column(db.Boolean) task_status_id = db.Column(UUIDType(binary=False), db.ForeignKey("task_status.id")) person_id = db.Column(UUIDType(binary=False), db.ForeignKey("person.id"), nullable=False) preview_file_id = db.Column(UUIDType(binary=False), db.ForeignKey("preview_file.id")) previews = db.relationship("PreviewFile", secondary=preview_link_table, backref="comments") mentions = db.relationship("Person", secondary=mentions_table) def __repr__(self): return "<Comment of %s>" % self.object_id
class EntityType(db.Model, BaseMixin, SerializerMixin): """ Type of entities. It can describe either an asset type, or tell if target entity is a shot, sequence, episode or layout scene. """ name = db.Column(db.String(30), unique=True, nullable=False, index=True)
class StatusAutomation(db.Model, BaseMixin, SerializerMixin): """ Status automations are entries that allow to describe changes that should automatically apply after a task status is changed. For instance, a Modeling task set to done will imply to set the Rigging task status to ready and the *ready_for* field to be set at Layout. """ entity_type = db.Column(db.String(40), default="asset") in_task_type_id = db.Column(UUIDType(binary=False), db.ForeignKey("task_type.id"), index=True) in_task_status_id = db.Column(UUIDType(binary=False), db.ForeignKey("task_status.id"), index=True) out_field_type = db.Column(ChoiceType(CHANGE_TYPES), default="status") out_task_type_id = db.Column(UUIDType(binary=False), db.ForeignKey("task_type.id"), index=True) out_task_status_id = db.Column( UUIDType(binary=False), db.ForeignKey("task_status.id"), index=True, nullable=True, )
class Milestone(db.Model, BaseMixin, SerializerMixin): """ Allow to set a milestone date to the production schedule. """ date = db.Column(db.Date()) name = db.Column(db.String(40), nullable=False) project_id = db.Column( UUIDType(binary=False), db.ForeignKey("project.id"), index=True ) task_type_id = db.Column( UUIDType(binary=False), db.ForeignKey("task_type.id"), index=True ) def present(self): return fields.serialize_dict( { "id": self.id, "date": self.date, "name": self.name, "project_id": self.project_id, "task_type_id": self.task_type_id, } )
class EntityLink(db.Model, BaseMixin, SerializerMixin): __tablename__ = "entity_link" entity_in_id = db.Column( UUIDType(binary=False), db.ForeignKey("entity.id"), primary_key=True, index=True ) entity_out_id = db.Column( UUIDType(binary=False), db.ForeignKey("entity.id"), primary_key=True, index=True ) nb_occurences = db.Column(db.Integer, default=1) label = db.Column(db.String(80), default="") @classmethod def create_from_import(cls, data): del data["type"] if "project_name" in data: del data["project_name"] entity_link = cls.get_by( entity_in_id=data["entity_in_id"], entity_out_id=data["entity_out_id"], ) if entity_link is None: return cls.create(**data) else: entity_link.update(data) return entity_link