Init Repo
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from typing import Optional, List, Dict
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
import re
|
||||
|
||||
from models.project import ProjectStatus, ProjectType
|
||||
from models.user import DepartmentRole
|
||||
|
||||
|
||||
# Technical Specifications Schemas
|
||||
class DeliveryMovieSpec(BaseModel):
|
||||
"""Delivery movie specifications for a department"""
|
||||
resolution: str = Field(..., description="Movie resolution (e.g., '1920x1080', '2048x1080')")
|
||||
format: str = Field(..., description="Movie format (e.g., 'mov', 'mp4', 'exr')")
|
||||
codec: Optional[str] = Field(None, description="Video codec (e.g., 'h264', 'prores', 'dnxhd')")
|
||||
quality: Optional[str] = Field(None, description="Quality setting (e.g., 'high', 'medium', 'low')")
|
||||
|
||||
@validator('resolution')
|
||||
def validate_resolution(cls, v):
|
||||
if not re.match(r'^\d+x\d+$', v):
|
||||
raise ValueError('Resolution must be in format "WIDTHxHEIGHT" (e.g., "1920x1080")')
|
||||
return v
|
||||
|
||||
@validator('format')
|
||||
def validate_format(cls, v):
|
||||
allowed_formats = ['mov', 'mp4', 'exr', 'dpx', 'tiff']
|
||||
if v.lower() not in allowed_formats:
|
||||
raise ValueError(f'Format must be one of: {", ".join(allowed_formats)}')
|
||||
return v.lower()
|
||||
|
||||
@validator('codec')
|
||||
def validate_codec(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
allowed_codecs = ['h264', 'h265', 'prores', 'dnxhd', 'dnxhr', 'uncompressed']
|
||||
if v.lower() not in allowed_codecs:
|
||||
raise ValueError(f'Codec must be one of: {", ".join(allowed_codecs)}')
|
||||
return v.lower()
|
||||
|
||||
@validator('quality')
|
||||
def validate_quality(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
allowed_qualities = ['low', 'medium', 'high', 'lossless']
|
||||
if v.lower() not in allowed_qualities:
|
||||
raise ValueError(f'Quality must be one of: {", ".join(allowed_qualities)}')
|
||||
return v.lower()
|
||||
|
||||
|
||||
class ProjectTechnicalSpecs(BaseModel):
|
||||
"""Technical specifications for a project"""
|
||||
frame_rate: Optional[float] = Field(None, ge=1.0, le=120.0, description="Frames per second (1-120 fps)")
|
||||
data_drive_path: Optional[str] = Field(None, description="Physical path for project data storage")
|
||||
publish_storage_path: Optional[str] = Field(None, description="Path for approved work delivery")
|
||||
delivery_image_resolution: Optional[str] = Field(None, description="Required image resolution (e.g., '1920x1080', '4096x2160')")
|
||||
delivery_movie_specs_by_department: Optional[Dict[str, DeliveryMovieSpec]] = Field(
|
||||
default_factory=dict,
|
||||
description="Delivery movie resolution and format specifications per department"
|
||||
)
|
||||
|
||||
@validator('delivery_image_resolution')
|
||||
def validate_delivery_image_resolution(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if not re.match(r'^\d+x\d+$', v):
|
||||
raise ValueError('Delivery image resolution must be in format "WIDTHxHEIGHT" (e.g., "1920x1080")')
|
||||
return v
|
||||
|
||||
@validator('delivery_movie_specs_by_department')
|
||||
def validate_delivery_movie_specs_by_department(cls, v):
|
||||
if v is None:
|
||||
return {}
|
||||
allowed_departments = ['layout', 'animation', 'lighting', 'composite', 'modeling', 'rigging', 'surfacing']
|
||||
for dept in v.keys():
|
||||
if dept not in allowed_departments:
|
||||
raise ValueError(f'Department must be one of: {", ".join(allowed_departments)}')
|
||||
return v
|
||||
|
||||
|
||||
# Default delivery movie specifications per department
|
||||
DEFAULT_DELIVERY_MOVIE_SPECS = {
|
||||
"layout": DeliveryMovieSpec(
|
||||
resolution="1920x1080",
|
||||
format="mov",
|
||||
codec="h264",
|
||||
quality="medium"
|
||||
),
|
||||
"animation": DeliveryMovieSpec(
|
||||
resolution="1920x1080",
|
||||
format="mov",
|
||||
codec="h264",
|
||||
quality="high"
|
||||
),
|
||||
"lighting": DeliveryMovieSpec(
|
||||
resolution="2048x1080",
|
||||
format="exr",
|
||||
codec=None,
|
||||
quality="high"
|
||||
),
|
||||
"composite": DeliveryMovieSpec(
|
||||
resolution="2048x1080",
|
||||
format="mov",
|
||||
codec="prores",
|
||||
quality="high"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class ProjectBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
code_name: str = Field(..., min_length=1, max_length=50, description="Unique project code identifier")
|
||||
client_name: str = Field(..., min_length=1, max_length=255, description="Client or studio name")
|
||||
project_type: ProjectType = Field(..., description="Project type: TV, Cinema, or Game")
|
||||
description: Optional[str] = None
|
||||
status: ProjectStatus = ProjectStatus.PLANNING
|
||||
start_date: Optional[date] = None
|
||||
end_date: Optional[date] = None
|
||||
|
||||
# Technical specifications
|
||||
frame_rate: Optional[float] = Field(None, ge=1.0, le=120.0, description="Frames per second (1-120 fps)")
|
||||
data_drive_path: Optional[str] = Field(None, description="Physical path for project data storage")
|
||||
publish_storage_path: Optional[str] = Field(None, description="Path for approved work delivery")
|
||||
delivery_image_resolution: Optional[str] = Field(None, description="Required image resolution")
|
||||
delivery_movie_specs_by_department: Optional[Dict[str, DeliveryMovieSpec]] = Field(
|
||||
default_factory=dict,
|
||||
description="Delivery movie specifications per department"
|
||||
)
|
||||
|
||||
@validator('delivery_image_resolution')
|
||||
def validate_delivery_image_resolution(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if not re.match(r'^\d+x\d+$', v):
|
||||
raise ValueError('Delivery image resolution must be in format "WIDTHxHEIGHT" (e.g., "1920x1080")')
|
||||
return v
|
||||
|
||||
@validator('delivery_movie_specs_by_department')
|
||||
def validate_delivery_movie_specs_by_department(cls, v):
|
||||
if v is None:
|
||||
return {}
|
||||
allowed_departments = ['layout', 'animation', 'lighting', 'composite', 'modeling', 'rigging', 'surfacing']
|
||||
for dept in v.keys():
|
||||
if dept not in allowed_departments:
|
||||
raise ValueError(f'Department must be one of: {", ".join(allowed_departments)}')
|
||||
return v
|
||||
|
||||
|
||||
class ProjectCreate(ProjectBase):
|
||||
pass
|
||||
|
||||
|
||||
class ProjectUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
code_name: Optional[str] = Field(None, min_length=1, max_length=50, description="Unique project code identifier")
|
||||
client_name: Optional[str] = Field(None, min_length=1, max_length=255, description="Client or studio name")
|
||||
project_type: Optional[ProjectType] = Field(None, description="Project type: TV, Cinema, or Game")
|
||||
description: Optional[str] = None
|
||||
status: Optional[ProjectStatus] = None
|
||||
start_date: Optional[date] = None
|
||||
end_date: Optional[date] = None
|
||||
|
||||
# Technical specifications
|
||||
frame_rate: Optional[float] = Field(None, ge=1.0, le=120.0, description="Frames per second (1-120 fps)")
|
||||
data_drive_path: Optional[str] = Field(None, description="Physical path for project data storage")
|
||||
publish_storage_path: Optional[str] = Field(None, description="Path for approved work delivery")
|
||||
delivery_image_resolution: Optional[str] = Field(None, description="Required image resolution")
|
||||
delivery_movie_specs_by_department: Optional[Dict[str, DeliveryMovieSpec]] = Field(
|
||||
None,
|
||||
description="Delivery movie specifications per department"
|
||||
)
|
||||
|
||||
@validator('delivery_image_resolution')
|
||||
def validate_delivery_image_resolution(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if not re.match(r'^\d+x\d+$', v):
|
||||
raise ValueError('Delivery image resolution must be in format "WIDTHxHEIGHT" (e.g., "1920x1080")')
|
||||
return v
|
||||
|
||||
@validator('delivery_movie_specs_by_department')
|
||||
def validate_delivery_movie_specs_by_department(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
allowed_departments = ['layout', 'animation', 'lighting', 'composite', 'modeling', 'rigging', 'surfacing']
|
||||
for dept in v.keys():
|
||||
if dept not in allowed_departments:
|
||||
raise ValueError(f'Department must be one of: {", ".join(allowed_departments)}')
|
||||
return v
|
||||
|
||||
|
||||
class ProjectMemberBase(BaseModel):
|
||||
user_id: int
|
||||
department_role: Optional[DepartmentRole] = None
|
||||
|
||||
|
||||
class ProjectMemberCreate(ProjectMemberBase):
|
||||
pass
|
||||
|
||||
|
||||
class ProjectMemberUpdate(BaseModel):
|
||||
department_role: Optional[DepartmentRole] = None
|
||||
|
||||
|
||||
class ProjectMemberResponse(ProjectMemberBase):
|
||||
id: int
|
||||
project_id: int
|
||||
joined_at: datetime
|
||||
|
||||
# User information
|
||||
user_email: str
|
||||
user_first_name: str
|
||||
user_last_name: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ProjectResponse(ProjectBase):
|
||||
id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
thumbnail_url: Optional[str] = None
|
||||
|
||||
# Optional member list
|
||||
project_members: Optional[List[ProjectMemberResponse]] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ProjectListResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
code_name: str
|
||||
client_name: str
|
||||
project_type: ProjectType
|
||||
description: Optional[str] = None
|
||||
status: ProjectStatus
|
||||
start_date: Optional[date] = None
|
||||
end_date: Optional[date] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
thumbnail_url: Optional[str] = None
|
||||
|
||||
# Summary information
|
||||
member_count: int = 0
|
||||
episode_count: int = 0
|
||||
asset_count: int = 0
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Default asset tasks by category
|
||||
DEFAULT_ASSET_TASKS = {
|
||||
"characters": ["modeling", "surfacing", "rigging"],
|
||||
"props": ["modeling", "surfacing"],
|
||||
"sets": ["modeling", "surfacing"],
|
||||
"vehicles": ["modeling", "surfacing", "rigging"]
|
||||
}
|
||||
|
||||
# Default shot tasks
|
||||
DEFAULT_SHOT_TASKS = ["layout", "animation", "simulation", "lighting", "compositing"]
|
||||
|
||||
|
||||
class ProjectSettings(BaseModel):
|
||||
"""Project-specific settings for upload location and task templates"""
|
||||
upload_data_location: Optional[str] = Field(None, description="Custom upload storage path for project files")
|
||||
asset_task_templates: Optional[Dict[str, List[str]]] = Field(
|
||||
default_factory=lambda: DEFAULT_ASSET_TASKS.copy(),
|
||||
description="Custom default tasks per asset category"
|
||||
)
|
||||
shot_task_templates: Optional[List[str]] = Field(
|
||||
default_factory=lambda: DEFAULT_SHOT_TASKS.copy(),
|
||||
description="Custom default tasks for shots"
|
||||
)
|
||||
enabled_asset_tasks: Optional[Dict[str, List[str]]] = Field(
|
||||
default_factory=dict,
|
||||
description="Enabled/disabled status for asset tasks per category"
|
||||
)
|
||||
enabled_shot_tasks: Optional[List[str]] = Field(
|
||||
default_factory=lambda: DEFAULT_SHOT_TASKS.copy(),
|
||||
description="Enabled/disabled shot tasks"
|
||||
)
|
||||
|
||||
@validator('asset_task_templates')
|
||||
def validate_asset_task_templates(cls, v):
|
||||
if v is None:
|
||||
return DEFAULT_ASSET_TASKS.copy()
|
||||
allowed_categories = ['characters', 'props', 'sets', 'vehicles']
|
||||
for category in v.keys():
|
||||
if category not in allowed_categories:
|
||||
raise ValueError(f'Asset category must be one of: {", ".join(allowed_categories)}')
|
||||
if not isinstance(v[category], list):
|
||||
raise ValueError(f'Task templates for {category} must be a list')
|
||||
return v
|
||||
|
||||
@validator('shot_task_templates')
|
||||
def validate_shot_task_templates(cls, v):
|
||||
if v is None:
|
||||
return DEFAULT_SHOT_TASKS.copy()
|
||||
if not isinstance(v, list):
|
||||
raise ValueError('Shot task templates must be a list')
|
||||
return v
|
||||
|
||||
@validator('enabled_asset_tasks')
|
||||
def validate_enabled_asset_tasks(cls, v):
|
||||
if v is None:
|
||||
return {}
|
||||
allowed_categories = ['characters', 'props', 'sets', 'vehicles']
|
||||
for category in v.keys():
|
||||
if category not in allowed_categories:
|
||||
raise ValueError(f'Asset category must be one of: {", ".join(allowed_categories)}')
|
||||
if not isinstance(v[category], list):
|
||||
raise ValueError(f'Enabled tasks for {category} must be a list')
|
||||
return v
|
||||
|
||||
@validator('enabled_shot_tasks')
|
||||
def validate_enabled_shot_tasks(cls, v):
|
||||
if v is None:
|
||||
return DEFAULT_SHOT_TASKS.copy()
|
||||
if not isinstance(v, list):
|
||||
raise ValueError('Enabled shot tasks must be a list')
|
||||
return v
|
||||
|
||||
|
||||
class ProjectSettingsUpdate(BaseModel):
|
||||
"""Update project settings"""
|
||||
upload_data_location: Optional[str] = Field(None, description="Custom upload storage path for project files")
|
||||
asset_task_templates: Optional[Dict[str, List[str]]] = Field(None, description="Custom default tasks per asset category")
|
||||
shot_task_templates: Optional[List[str]] = Field(None, description="Custom default tasks for shots")
|
||||
enabled_asset_tasks: Optional[Dict[str, List[str]]] = Field(None, description="Enabled/disabled asset tasks per category")
|
||||
enabled_shot_tasks: Optional[List[str]] = Field(None, description="Enabled/disabled shot tasks")
|
||||
|
||||
@validator('asset_task_templates')
|
||||
def validate_asset_task_templates(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
allowed_categories = ['characters', 'props', 'sets', 'vehicles']
|
||||
for category in v.keys():
|
||||
if category not in allowed_categories:
|
||||
raise ValueError(f'Asset category must be one of: {", ".join(allowed_categories)}')
|
||||
if not isinstance(v[category], list):
|
||||
raise ValueError(f'Task templates for {category} must be a list')
|
||||
return v
|
||||
|
||||
@validator('shot_task_templates')
|
||||
def validate_shot_task_templates(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if not isinstance(v, list):
|
||||
raise ValueError('Shot task templates must be a list')
|
||||
return v
|
||||
|
||||
@validator('enabled_asset_tasks')
|
||||
def validate_enabled_asset_tasks(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
allowed_categories = ['characters', 'props', 'sets', 'vehicles']
|
||||
for category in v.keys():
|
||||
if category not in allowed_categories:
|
||||
raise ValueError(f'Asset category must be one of: {", ".join(allowed_categories)}')
|
||||
if not isinstance(v[category], list):
|
||||
raise ValueError(f'Enabled tasks for {category} must be a list')
|
||||
return v
|
||||
|
||||
@validator('enabled_shot_tasks')
|
||||
def validate_enabled_shot_tasks(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if not isinstance(v, list):
|
||||
raise ValueError('Enabled shot tasks must be a list')
|
||||
return v
|
||||
Reference in New Issue
Block a user