23740af816
Coordinators can configure, per task type, accepted file extensions, a
naming convention, and (for video files) required resolution/format/
codec/frame rate. All checking happens entirely client-side in the
browser before upload - the backend only stores and validates the
configuration itself, never inspects submitted files.
- backend: submission_config_by_task_type JSON column on Project,
schemas/submission_config.py for validation, GET/PUT
/projects/{id}/submission-config endpoints. Added .mxf/.ma/.usd(a/c)
to the supported file formats.
- frontend: new Submissions tab in Project Settings
(SubmissionConfigManager.vue) to configure rules per task type.
TaskSubmissions.vue enforces them before upload: extension/naming
checks via regex, and real video resolution/codec/frame rate
detection via mediainfo.js (parses container metadata directly,
so it works for formats browsers can't play natively like MXF,
ProRes, or DNxHD).
91 lines
4.1 KiB
Python
91 lines
4.1 KiB
Python
from sqlalchemy import Column, Integer, String, DateTime, Date, Enum, ForeignKey, Float, JSON
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.sql import func
|
|
from database import Base
|
|
from .user import DepartmentRole
|
|
import enum
|
|
|
|
|
|
class ProjectStatus(str, enum.Enum):
|
|
PLANNING = "planning"
|
|
IN_PROGRESS = "in_progress"
|
|
ON_HOLD = "on_hold"
|
|
COMPLETED = "completed"
|
|
CANCELLED = "cancelled"
|
|
|
|
|
|
class ProjectType(str, enum.Enum):
|
|
TV = "tv"
|
|
CINEMA = "cinema"
|
|
GAME = "game"
|
|
|
|
|
|
class Project(Base):
|
|
__tablename__ = "projects"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
name = Column(String, nullable=False, index=True)
|
|
code_name = Column(String, nullable=False, unique=True, index=True) # Unique project code
|
|
client_name = Column(String, nullable=False, index=True) # Client/studio name
|
|
project_type = Column(Enum(ProjectType), nullable=False) # TV, Cinema, Game
|
|
description = Column(String)
|
|
status = Column(Enum(ProjectStatus), nullable=False, default=ProjectStatus.PLANNING)
|
|
start_date = Column(Date)
|
|
end_date = Column(Date)
|
|
|
|
# Technical specifications
|
|
frame_rate = Column(Float, nullable=True) # Frames per second (1-120 fps)
|
|
data_drive_path = Column(String, nullable=True) # Physical path for project data storage
|
|
publish_storage_path = Column(String, nullable=True) # Path for approved work delivery
|
|
delivery_image_resolution = Column(String, nullable=True) # Required image resolution
|
|
delivery_movie_specs_by_department = Column(JSON, nullable=True) # Delivery movie specs per department
|
|
|
|
# Project-specific settings
|
|
upload_data_location = Column(String, nullable=True) # Custom upload storage path
|
|
asset_task_templates = Column(JSON, nullable=True) # Default tasks per asset category
|
|
shot_task_templates = Column(JSON, nullable=True) # Default tasks for shots
|
|
enabled_asset_tasks = Column(JSON, nullable=True) # Enabled/disabled asset tasks per category
|
|
enabled_shot_tasks = Column(JSON, nullable=True) # Enabled/disabled shot tasks
|
|
|
|
# Custom task types
|
|
custom_asset_task_types = Column(JSON, nullable=True) # Custom task types for assets
|
|
custom_shot_task_types = Column(JSON, nullable=True) # Custom task types for shots
|
|
|
|
# Custom task statuses
|
|
custom_task_statuses = Column(JSON, nullable=True) # Custom task statuses for project
|
|
|
|
# Submission configuration per task type
|
|
submission_config_by_task_type = Column(JSON, nullable=True) # Allowed file types, naming pattern, required flag per task type
|
|
|
|
# Project thumbnail
|
|
thumbnail_path = Column(String, nullable=True) # Path to project thumbnail image
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
# Relationships
|
|
episodes = relationship("Episode", back_populates="project", cascade="all, delete-orphan")
|
|
shots = relationship("Shot", back_populates="project", cascade="all, delete-orphan")
|
|
assets = relationship("Asset", back_populates="project", cascade="all, delete-orphan")
|
|
project_members = relationship("ProjectMember", back_populates="project", cascade="all, delete-orphan")
|
|
tasks = relationship("Task", back_populates="project", cascade="all, delete-orphan")
|
|
|
|
def __repr__(self):
|
|
return f"<Project(id={self.id}, name='{self.name}', status='{self.status}')>"
|
|
|
|
|
|
class ProjectMember(Base):
|
|
__tablename__ = "project_members"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
|
|
department_role = Column(Enum(DepartmentRole), nullable=True)
|
|
joined_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
user = relationship("User", back_populates="project_memberships")
|
|
project = relationship("Project", back_populates="project_members")
|
|
|
|
def __repr__(self):
|
|
return f"<ProjectMember(user_id={self.user_id}, project_id={self.project_id}, role='{self.department_role}')>" |