Init Repo
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
from .auth import UserLogin, UserRegister, Token, TokenData, RefreshToken
|
||||
from .user import UserBase, UserCreate, UserUpdate, UserResponse, UserApproval, UserRoleUpdate
|
||||
from .project import (
|
||||
ProjectBase, ProjectCreate, ProjectUpdate, ProjectResponse, ProjectListResponse,
|
||||
ProjectMemberBase, ProjectMemberCreate, ProjectMemberUpdate, ProjectMemberResponse
|
||||
)
|
||||
from .episode import EpisodeBase, EpisodeCreate, EpisodeUpdate, EpisodeResponse, EpisodeListResponse
|
||||
from .task import (
|
||||
TaskBase, TaskCreate, TaskUpdate, TaskResponse, TaskListResponse, TaskStatusUpdate, TaskAssignment,
|
||||
ProductionNoteBase, ProductionNoteCreate, ProductionNoteUpdate, ProductionNoteResponse,
|
||||
TaskAttachmentBase, TaskAttachmentCreate, TaskAttachmentResponse,
|
||||
SubmissionBase, SubmissionCreate, SubmissionResponse,
|
||||
ReviewBase, ReviewCreate, ReviewResponse
|
||||
)
|
||||
@@ -0,0 +1,65 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ActivityType(str, Enum):
|
||||
TASK_CREATED = "task_created"
|
||||
TASK_UPDATED = "task_updated"
|
||||
TASK_ASSIGNED = "task_assigned"
|
||||
TASK_STATUS_CHANGED = "task_status_changed"
|
||||
SUBMISSION_CREATED = "submission_created"
|
||||
SUBMISSION_REVIEWED = "submission_reviewed"
|
||||
COMMENT_ADDED = "comment_added"
|
||||
ASSET_CREATED = "asset_created"
|
||||
ASSET_UPDATED = "asset_updated"
|
||||
SHOT_CREATED = "shot_created"
|
||||
SHOT_UPDATED = "shot_updated"
|
||||
PROJECT_CREATED = "project_created"
|
||||
PROJECT_UPDATED = "project_updated"
|
||||
USER_JOINED_PROJECT = "user_joined_project"
|
||||
SHOT_DELETED = "shot_deleted"
|
||||
ASSET_DELETED = "asset_deleted"
|
||||
SHOT_RECOVERED = "shot_recovered"
|
||||
ASSET_RECOVERED = "asset_recovered"
|
||||
|
||||
|
||||
class ActivityBase(BaseModel):
|
||||
type: ActivityType
|
||||
description: str
|
||||
activity_metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class ActivityCreate(ActivityBase):
|
||||
user_id: int
|
||||
project_id: Optional[int] = None
|
||||
task_id: Optional[int] = None
|
||||
asset_id: Optional[int] = None
|
||||
shot_id: Optional[int] = None
|
||||
submission_id: Optional[int] = None
|
||||
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
id: int
|
||||
first_name: str
|
||||
last_name: str
|
||||
email: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ActivityResponse(ActivityBase):
|
||||
id: int
|
||||
user_id: int
|
||||
user: UserInfo
|
||||
project_id: Optional[int] = None
|
||||
task_id: Optional[int] = None
|
||||
asset_id: Optional[int] = None
|
||||
shot_id: Optional[int] = None
|
||||
submission_id: Optional[int] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,50 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
from models.api_key import APIKeyScope
|
||||
|
||||
|
||||
class APIKeyCreate(BaseModel):
|
||||
name: str
|
||||
scopes: List[APIKeyScope]
|
||||
expires_at: Optional[datetime] = None
|
||||
user_id: Optional[int] = None # Only admins can specify this
|
||||
|
||||
|
||||
class APIKeyResponse(BaseModel):
|
||||
id: int
|
||||
user_id: int
|
||||
name: str
|
||||
scopes: List[str]
|
||||
is_active: bool
|
||||
expires_at: Optional[datetime]
|
||||
last_used_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
user_email: Optional[str] = None # Include user email for admin view
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class APIKeyWithToken(BaseModel):
|
||||
api_key: APIKeyResponse
|
||||
token: str # The actual API key token (only returned on creation)
|
||||
|
||||
|
||||
class APIKeyUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
scopes: Optional[List[APIKeyScope]] = None
|
||||
is_active: Optional[bool] = None
|
||||
expires_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class APIKeyUsageLog(BaseModel):
|
||||
api_key_id: int
|
||||
endpoint: str
|
||||
method: str
|
||||
timestamp: datetime
|
||||
ip_address: Optional[str] = None
|
||||
user_agent: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,66 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict
|
||||
from datetime import datetime
|
||||
|
||||
from models.asset import AssetCategory, AssetStatus
|
||||
from models.task import TaskType, TaskStatus
|
||||
|
||||
|
||||
class AssetBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
category: AssetCategory
|
||||
status: AssetStatus = AssetStatus.NOT_STARTED
|
||||
|
||||
|
||||
class AssetCreate(AssetBase):
|
||||
create_default_tasks: bool = Field(default=True, description="Whether to create default tasks for this asset")
|
||||
selected_task_types: Optional[List[str]] = Field(default=None, description="Specific task types to create (if None, uses category defaults)")
|
||||
|
||||
|
||||
class AssetUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
category: Optional[AssetCategory] = None
|
||||
status: Optional[AssetStatus] = None
|
||||
|
||||
|
||||
class AssetResponse(AssetBase):
|
||||
id: int
|
||||
project_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Summary information
|
||||
task_count: int = 0
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TaskStatusInfo(BaseModel):
|
||||
task_type: str # Changed from TaskType enum to str to support custom task types
|
||||
status: str # Changed from TaskStatus enum to str to support custom statuses
|
||||
task_id: Optional[int] = None
|
||||
assigned_user_id: Optional[int] = None
|
||||
|
||||
|
||||
class AssetListResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
category: AssetCategory
|
||||
status: AssetStatus
|
||||
project_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Summary information
|
||||
task_count: int = 0
|
||||
|
||||
# Task status information for table display
|
||||
task_status: Dict[str, Optional[str]] = Field(default_factory=dict, description="Task status by task type")
|
||||
task_details: List[TaskStatusInfo] = Field(default_factory=list, description="Detailed task information")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,30 @@
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from typing import Optional
|
||||
from models.user import UserRole
|
||||
|
||||
|
||||
class UserLogin(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
|
||||
class UserRegister(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
email: Optional[str] = None
|
||||
user_id: Optional[int] = None
|
||||
|
||||
|
||||
class RefreshToken(BaseModel):
|
||||
refresh_token: str
|
||||
@@ -0,0 +1,157 @@
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from typing import List, Optional
|
||||
import re
|
||||
|
||||
|
||||
class CustomTaskStatus(BaseModel):
|
||||
"""Schema for a custom task status"""
|
||||
id: str = Field(..., description="Unique identifier for the status")
|
||||
name: str = Field(..., min_length=1, max_length=50, description="Display name")
|
||||
color: str = Field(..., pattern=r'^#[0-9A-Fa-f]{6}$', description="Hex color code")
|
||||
order: int = Field(..., ge=0, description="Display order")
|
||||
is_default: bool = Field(default=False, description="Whether this is the default status")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class CustomTaskStatusCreate(BaseModel):
|
||||
"""Schema for creating a new custom task status"""
|
||||
name: str = Field(..., min_length=1, max_length=50, description="Status name")
|
||||
color: Optional[str] = Field(None, description="Hex color code (e.g., #FF5733)")
|
||||
|
||||
@validator('name')
|
||||
def validate_name(cls, v):
|
||||
"""Validate and normalize status name"""
|
||||
# Trim whitespace
|
||||
v = v.strip()
|
||||
if not v:
|
||||
raise ValueError('Status name cannot be empty')
|
||||
if len(v) > 50:
|
||||
raise ValueError('Status name cannot exceed 50 characters')
|
||||
return v
|
||||
|
||||
@validator('color')
|
||||
def validate_color(cls, v):
|
||||
"""Validate color format if provided"""
|
||||
if v is None:
|
||||
return v
|
||||
# Trim whitespace
|
||||
v = v.strip()
|
||||
# Check hex color format
|
||||
if not re.match(r'^#[0-9A-Fa-f]{6}$', v):
|
||||
raise ValueError('Color must be a valid hex code (e.g., #FF5733)')
|
||||
return v.upper() # Normalize to uppercase
|
||||
|
||||
|
||||
class CustomTaskStatusUpdate(BaseModel):
|
||||
"""Schema for updating a custom task status"""
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=50, description="Status name")
|
||||
color: Optional[str] = Field(None, description="Hex color code (e.g., #FF5733)")
|
||||
is_default: Optional[bool] = Field(None, description="Set as default status")
|
||||
|
||||
@validator('name')
|
||||
def validate_name(cls, v):
|
||||
"""Validate and normalize status name"""
|
||||
if v is None:
|
||||
return v
|
||||
# Trim whitespace
|
||||
v = v.strip()
|
||||
if not v:
|
||||
raise ValueError('Status name cannot be empty')
|
||||
if len(v) > 50:
|
||||
raise ValueError('Status name cannot exceed 50 characters')
|
||||
return v
|
||||
|
||||
@validator('color')
|
||||
def validate_color(cls, v):
|
||||
"""Validate color format if provided"""
|
||||
if v is None:
|
||||
return v
|
||||
# Trim whitespace
|
||||
v = v.strip()
|
||||
# Check hex color format
|
||||
if not re.match(r'^#[0-9A-Fa-f]{6}$', v):
|
||||
raise ValueError('Color must be a valid hex code (e.g., #FF5733)')
|
||||
return v.upper() # Normalize to uppercase
|
||||
|
||||
|
||||
class CustomTaskStatusReorder(BaseModel):
|
||||
"""Schema for reordering statuses"""
|
||||
status_ids: List[str] = Field(..., min_items=1, description="Ordered list of status IDs")
|
||||
|
||||
@validator('status_ids')
|
||||
def validate_status_ids(cls, v):
|
||||
"""Validate status IDs list"""
|
||||
if not v:
|
||||
raise ValueError('Status IDs list cannot be empty')
|
||||
# Check for duplicates
|
||||
if len(v) != len(set(v)):
|
||||
raise ValueError('Status IDs list contains duplicates')
|
||||
return v
|
||||
|
||||
|
||||
class CustomTaskStatusDelete(BaseModel):
|
||||
"""Schema for deleting a status with optional reassignment"""
|
||||
reassign_to_status_id: Optional[str] = Field(
|
||||
None,
|
||||
description="Status ID to reassign tasks to (required if status is in use)"
|
||||
)
|
||||
|
||||
|
||||
class SystemTaskStatus(BaseModel):
|
||||
"""Schema for system (built-in) task statuses"""
|
||||
id: str = Field(..., description="System status identifier")
|
||||
name: str = Field(..., description="Display name")
|
||||
color: str = Field(..., description="Hex color code")
|
||||
is_system: bool = Field(default=True, description="Indicates this is a system status")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class AllTaskStatusesResponse(BaseModel):
|
||||
"""Schema for response containing all task statuses (system + custom)"""
|
||||
statuses: List[CustomTaskStatus] = Field(
|
||||
default_factory=list,
|
||||
description="Custom task statuses defined for the project"
|
||||
)
|
||||
system_statuses: List[SystemTaskStatus] = Field(
|
||||
default_factory=list,
|
||||
description="Built-in system task statuses"
|
||||
)
|
||||
default_status_id: str = Field(
|
||||
...,
|
||||
description="ID of the default status for new tasks"
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TaskStatusInUseError(BaseModel):
|
||||
"""Schema for error when trying to delete a status that is in use"""
|
||||
error: str = Field(..., description="Error message")
|
||||
status_id: str = Field(..., description="ID of the status that cannot be deleted")
|
||||
status_name: str = Field(..., description="Name of the status that cannot be deleted")
|
||||
task_count: int = Field(..., ge=0, description="Number of tasks using this status")
|
||||
task_ids: List[int] = Field(
|
||||
default_factory=list,
|
||||
description="IDs of tasks using this status"
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class CustomTaskStatusResponse(BaseModel):
|
||||
"""Schema for successful custom task status operation response"""
|
||||
message: str = Field(..., description="Success message")
|
||||
status: Optional[CustomTaskStatus] = Field(None, description="The created or updated status")
|
||||
all_statuses: Optional[AllTaskStatusesResponse] = Field(
|
||||
None,
|
||||
description="All statuses after the operation"
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Pydantic schemas for custom task type management
|
||||
"""
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from typing import List, Literal
|
||||
import re
|
||||
|
||||
|
||||
class CustomTaskTypeCreate(BaseModel):
|
||||
"""Schema for creating a new custom task type"""
|
||||
task_type: str = Field(..., min_length=3, max_length=50, description="Task type name")
|
||||
category: Literal["asset", "shot"] = Field(..., description="Task category (asset or shot)")
|
||||
|
||||
@validator('task_type')
|
||||
def validate_task_type_name(cls, v):
|
||||
"""Validate task type name format"""
|
||||
# Must be lowercase alphanumeric with underscores only
|
||||
if not re.match(r'^[a-z0-9_]{3,50}$', v):
|
||||
raise ValueError(
|
||||
'Task type name must be 3-50 characters, lowercase alphanumeric with underscores only'
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
class CustomTaskTypeUpdate(BaseModel):
|
||||
"""Schema for updating a custom task type name"""
|
||||
old_name: str = Field(..., description="Current task type name")
|
||||
new_name: str = Field(..., min_length=3, max_length=50, description="New task type name")
|
||||
category: Literal["asset", "shot"] = Field(..., description="Task category (asset or shot)")
|
||||
|
||||
@validator('new_name')
|
||||
def validate_task_type_name(cls, v):
|
||||
"""Validate task type name format"""
|
||||
if not re.match(r'^[a-z0-9_]{3,50}$', v):
|
||||
raise ValueError(
|
||||
'Task type name must be 3-50 characters, lowercase alphanumeric with underscores only'
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
class CustomTaskTypeDelete(BaseModel):
|
||||
"""Schema for deleting a custom task type"""
|
||||
task_type: str = Field(..., description="Task type name to delete")
|
||||
category: Literal["asset", "shot"] = Field(..., description="Task category (asset or shot)")
|
||||
|
||||
|
||||
class AllTaskTypesResponse(BaseModel):
|
||||
"""Schema for response containing all task types (standard + custom)"""
|
||||
asset_task_types: List[str] = Field(..., description="All asset task types")
|
||||
shot_task_types: List[str] = Field(..., description="All shot task types")
|
||||
standard_asset_types: List[str] = Field(..., description="Standard asset task types (read-only)")
|
||||
standard_shot_types: List[str] = Field(..., description="Standard shot task types (read-only)")
|
||||
custom_asset_types: List[str] = Field(..., description="Custom asset task types")
|
||||
custom_shot_types: List[str] = Field(..., description="Custom shot task types")
|
||||
|
||||
|
||||
class TaskTypeInUseError(BaseModel):
|
||||
"""Schema for error when trying to delete a task type in use"""
|
||||
error: str = Field(..., description="Error message")
|
||||
task_type: str = Field(..., description="Task type that is in use")
|
||||
task_count: int = Field(..., description="Number of tasks using this type")
|
||||
task_ids: List[int] = Field(..., description="IDs of tasks using this type")
|
||||
@@ -0,0 +1,54 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from models.episode import EpisodeStatus
|
||||
|
||||
|
||||
class EpisodeBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
episode_number: int = Field(..., ge=1)
|
||||
status: EpisodeStatus = EpisodeStatus.PLANNING
|
||||
|
||||
|
||||
class EpisodeCreate(EpisodeBase):
|
||||
pass
|
||||
|
||||
|
||||
class EpisodeUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
episode_number: Optional[int] = Field(None, ge=1)
|
||||
status: Optional[EpisodeStatus] = None
|
||||
|
||||
|
||||
class EpisodeResponse(EpisodeBase):
|
||||
id: int
|
||||
project_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Summary information
|
||||
shot_count: int = 0
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class EpisodeListResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
episode_number: int
|
||||
status: EpisodeStatus
|
||||
project_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Summary information
|
||||
shot_count: int = 0
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,36 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class GlobalSettingBase(BaseModel):
|
||||
setting_key: str = Field(..., max_length=100)
|
||||
setting_value: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class GlobalSettingCreate(GlobalSettingBase):
|
||||
pass
|
||||
|
||||
|
||||
class GlobalSettingUpdate(BaseModel):
|
||||
setting_value: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class GlobalSetting(GlobalSettingBase):
|
||||
id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UploadLimitResponse(BaseModel):
|
||||
upload_limit_mb: int
|
||||
description: str
|
||||
|
||||
|
||||
class UploadLimitUpdate(BaseModel):
|
||||
upload_limit_mb: int = Field(..., ge=1, le=10000, description="Upload limit in MB (1-10000)")
|
||||
@@ -0,0 +1,98 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class NotificationType(str, Enum):
|
||||
TASK_ASSIGNED = "task_assigned"
|
||||
TASK_STATUS_CHANGED = "task_status_changed"
|
||||
SUBMISSION_REVIEWED = "submission_reviewed"
|
||||
WORK_SUBMITTED = "work_submitted"
|
||||
DEADLINE_APPROACHING = "deadline_approaching"
|
||||
PROJECT_UPDATE = "project_update"
|
||||
COMMENT_ADDED = "comment_added"
|
||||
|
||||
|
||||
class NotificationPriority(str, Enum):
|
||||
LOW = "low"
|
||||
NORMAL = "normal"
|
||||
HIGH = "high"
|
||||
URGENT = "urgent"
|
||||
|
||||
|
||||
class NotificationBase(BaseModel):
|
||||
type: NotificationType
|
||||
priority: NotificationPriority = NotificationPriority.NORMAL
|
||||
title: str = Field(..., max_length=255)
|
||||
message: str
|
||||
project_id: Optional[int] = None
|
||||
task_id: Optional[int] = None
|
||||
submission_id: Optional[int] = None
|
||||
|
||||
|
||||
class NotificationCreate(NotificationBase):
|
||||
user_id: int
|
||||
|
||||
|
||||
class NotificationResponse(NotificationBase):
|
||||
id: int
|
||||
user_id: int
|
||||
read: bool
|
||||
email_sent: bool
|
||||
email_sent_at: Optional[datetime] = None
|
||||
created_at: datetime
|
||||
read_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class NotificationMarkRead(BaseModel):
|
||||
notification_ids: list[int]
|
||||
|
||||
|
||||
class NotificationPreferencesBase(BaseModel):
|
||||
# Email notification preferences
|
||||
email_enabled: bool = True
|
||||
email_task_assigned: bool = True
|
||||
email_task_status_changed: bool = True
|
||||
email_submission_reviewed: bool = True
|
||||
email_work_submitted: bool = True
|
||||
email_deadline_approaching: bool = True
|
||||
email_project_update: bool = True
|
||||
email_comment_added: bool = True
|
||||
|
||||
# In-app notification preferences
|
||||
inapp_enabled: bool = True
|
||||
inapp_task_assigned: bool = True
|
||||
inapp_task_status_changed: bool = True
|
||||
inapp_submission_reviewed: bool = True
|
||||
inapp_work_submitted: bool = True
|
||||
inapp_deadline_approaching: bool = True
|
||||
inapp_project_update: bool = True
|
||||
inapp_comment_added: bool = True
|
||||
|
||||
# Digest settings
|
||||
email_digest_enabled: bool = False
|
||||
email_digest_frequency: str = "daily"
|
||||
|
||||
|
||||
class NotificationPreferencesUpdate(NotificationPreferencesBase):
|
||||
pass
|
||||
|
||||
|
||||
class NotificationPreferencesResponse(NotificationPreferencesBase):
|
||||
id: int
|
||||
user_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class NotificationStats(BaseModel):
|
||||
total: int
|
||||
unread: int
|
||||
by_type: dict[str, int]
|
||||
@@ -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
|
||||
@@ -0,0 +1,99 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict
|
||||
from datetime import datetime
|
||||
|
||||
from models.shot import ShotStatus
|
||||
from models.task import TaskType, TaskStatus
|
||||
|
||||
|
||||
class ShotBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
frame_start: int = Field(default=1001, ge=1)
|
||||
frame_end: int = Field(default=1001, ge=1)
|
||||
status: ShotStatus = ShotStatus.NOT_STARTED
|
||||
project_id: Optional[int] = Field(None, description="Project ID - auto-populated from episode if not provided")
|
||||
|
||||
|
||||
class ShotCreate(ShotBase):
|
||||
pass
|
||||
|
||||
|
||||
class ShotUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
frame_start: Optional[int] = Field(None, ge=1)
|
||||
frame_end: Optional[int] = Field(None, ge=1)
|
||||
status: Optional[ShotStatus] = None
|
||||
project_id: Optional[int] = Field(None, description="Project ID - must match episode's project")
|
||||
|
||||
|
||||
class ShotResponse(ShotBase):
|
||||
id: int
|
||||
project_id: int # Make required in response
|
||||
episode_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Summary information
|
||||
task_count: int = 0
|
||||
|
||||
# Optional computed field for display
|
||||
project_name: Optional[str] = Field(None, description="Project name for display purposes")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TaskStatusInfo(BaseModel):
|
||||
"""Task status information for table display"""
|
||||
task_type: str # String to support custom task types
|
||||
status: str # Changed from TaskStatus enum to str to support custom statuses
|
||||
task_id: Optional[int] = None
|
||||
assigned_user_id: Optional[int] = None
|
||||
|
||||
|
||||
class ShotListResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
frame_start: int
|
||||
frame_end: int
|
||||
status: ShotStatus
|
||||
project_id: int
|
||||
episode_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Summary information
|
||||
task_count: int = 0
|
||||
|
||||
# Optional computed field for display
|
||||
project_name: Optional[str] = Field(None, description="Project name for display purposes")
|
||||
|
||||
# Task status information for table display
|
||||
task_status: Dict[str, Optional[str]] = Field(default_factory=dict, description="Task status by task type")
|
||||
task_details: List[TaskStatusInfo] = Field(default_factory=list, description="Detailed task information")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class BulkShotCreate(BaseModel):
|
||||
"""Schema for bulk shot creation with naming patterns"""
|
||||
name_prefix: str = Field(..., min_length=1, max_length=100)
|
||||
shot_count: int = Field(..., ge=1, le=1000)
|
||||
start_number: int = Field(default=10, ge=1)
|
||||
number_padding: int = Field(default=3, ge=1, le=6)
|
||||
frame_start: int = Field(default=1001, ge=1)
|
||||
frame_end: int = Field(default=1001, ge=1)
|
||||
description_template: Optional[str] = None
|
||||
create_default_tasks: bool = Field(default=True)
|
||||
task_types: Optional[List[str]] = None # If None, use default shot task types (changed from TaskType to str)
|
||||
|
||||
|
||||
class BulkShotResponse(BaseModel):
|
||||
"""Response for bulk shot creation"""
|
||||
created_shots: List[ShotResponse]
|
||||
created_tasks_count: int = 0
|
||||
message: str
|
||||
@@ -0,0 +1,230 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
|
||||
from models.task import TaskType, TaskStatus, ReviewDecision, AttachmentType
|
||||
from models.user import DepartmentRole
|
||||
|
||||
|
||||
class TaskBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
task_type: str # Changed from TaskType enum to str to support custom task types
|
||||
deadline: Optional[date] = None
|
||||
status: str = "not_started" # Changed from TaskStatus enum to str to support custom statuses
|
||||
|
||||
|
||||
class TaskCreate(TaskBase):
|
||||
project_id: int
|
||||
episode_id: Optional[int] = None
|
||||
shot_id: Optional[int] = None
|
||||
asset_id: Optional[int] = None
|
||||
assigned_user_id: Optional[int] = None
|
||||
|
||||
|
||||
class TaskUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
task_type: Optional[str] = None # Changed from TaskType enum to str to support custom task types
|
||||
deadline: Optional[date] = None
|
||||
status: Optional[str] = None # Changed from TaskStatus enum to str to support custom statuses
|
||||
assigned_user_id: Optional[int] = None
|
||||
|
||||
|
||||
class TaskStatusUpdate(BaseModel):
|
||||
status: str # Changed from TaskStatus enum to str to support custom statuses
|
||||
|
||||
|
||||
class TaskAssignment(BaseModel):
|
||||
assigned_user_id: int
|
||||
|
||||
|
||||
class TaskResponse(TaskBase):
|
||||
id: int
|
||||
project_id: int
|
||||
episode_id: Optional[int] = None
|
||||
shot_id: Optional[int] = None
|
||||
asset_id: Optional[int] = None
|
||||
assigned_user_id: Optional[int] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Related entity names for display
|
||||
project_name: Optional[str] = None
|
||||
episode_name: Optional[str] = None
|
||||
shot_name: Optional[str] = None
|
||||
asset_name: Optional[str] = None
|
||||
assigned_user_name: Optional[str] = None
|
||||
assigned_user_email: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TaskListResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
task_type: str # Changed from TaskType enum to str to support custom task types
|
||||
status: str # Changed from TaskStatus enum to str to support custom statuses
|
||||
deadline: Optional[date] = None
|
||||
project_id: int
|
||||
project_name: str
|
||||
episode_id: Optional[int] = None
|
||||
episode_name: Optional[str] = None
|
||||
shot_id: Optional[int] = None
|
||||
shot_name: Optional[str] = None
|
||||
asset_id: Optional[int] = None
|
||||
asset_name: Optional[str] = None
|
||||
assigned_user_id: Optional[int] = None
|
||||
assigned_user_name: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Production Notes schemas
|
||||
class ProductionNoteBase(BaseModel):
|
||||
content: str = Field(..., min_length=1)
|
||||
parent_note_id: Optional[int] = None
|
||||
|
||||
|
||||
class ProductionNoteCreate(ProductionNoteBase):
|
||||
pass
|
||||
|
||||
|
||||
class ProductionNoteUpdate(BaseModel):
|
||||
content: str = Field(..., min_length=1)
|
||||
|
||||
|
||||
class ProductionNoteResponse(ProductionNoteBase):
|
||||
id: int
|
||||
task_id: int
|
||||
user_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# User information
|
||||
user_first_name: str
|
||||
user_last_name: str
|
||||
user_email: str
|
||||
|
||||
# Child notes for threading
|
||||
child_notes: Optional[List['ProductionNoteResponse']] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Task Attachment schemas
|
||||
class TaskAttachmentBase(BaseModel):
|
||||
file_name: str
|
||||
attachment_type: AttachmentType = AttachmentType.REFERENCE
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class TaskAttachmentCreate(TaskAttachmentBase):
|
||||
pass
|
||||
|
||||
|
||||
class TaskAttachmentResponse(TaskAttachmentBase):
|
||||
id: int
|
||||
task_id: int
|
||||
user_id: int
|
||||
file_path: str
|
||||
file_type: str
|
||||
file_size: int
|
||||
uploaded_at: datetime
|
||||
|
||||
# User information
|
||||
user_first_name: str
|
||||
user_last_name: str
|
||||
|
||||
# File serving URLs (computed properties)
|
||||
download_url: Optional[str] = None
|
||||
thumbnail_url: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Submission schemas
|
||||
class SubmissionBase(BaseModel):
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class SubmissionCreate(SubmissionBase):
|
||||
pass
|
||||
|
||||
|
||||
class SubmissionResponse(SubmissionBase):
|
||||
id: int
|
||||
task_id: int
|
||||
user_id: int
|
||||
file_path: str
|
||||
file_name: str
|
||||
version_number: int
|
||||
submitted_at: datetime
|
||||
|
||||
# User information
|
||||
user_first_name: str
|
||||
user_last_name: str
|
||||
|
||||
# Latest review if any
|
||||
latest_review: Optional['ReviewResponse'] = None
|
||||
|
||||
# File serving URLs (computed properties)
|
||||
download_url: Optional[str] = None
|
||||
thumbnail_url: Optional[str] = None
|
||||
stream_url: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Review schemas
|
||||
class ReviewBase(BaseModel):
|
||||
decision: ReviewDecision
|
||||
feedback: Optional[str] = None
|
||||
|
||||
|
||||
class ReviewCreate(ReviewBase):
|
||||
pass
|
||||
|
||||
|
||||
class ReviewResponse(ReviewBase):
|
||||
id: int
|
||||
submission_id: int
|
||||
reviewer_id: int
|
||||
reviewed_at: datetime
|
||||
|
||||
# Reviewer information
|
||||
reviewer_first_name: str
|
||||
reviewer_last_name: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Bulk action schemas
|
||||
class BulkStatusUpdate(BaseModel):
|
||||
task_ids: List[int] = Field(..., min_length=1)
|
||||
status: TaskStatus
|
||||
|
||||
|
||||
class BulkAssignment(BaseModel):
|
||||
task_ids: List[int] = Field(..., min_length=1)
|
||||
assigned_user_id: int
|
||||
|
||||
|
||||
class BulkActionResult(BaseModel):
|
||||
success_count: int
|
||||
failed_count: int
|
||||
errors: Optional[List[dict]] = None
|
||||
|
||||
|
||||
# Update forward references
|
||||
ProductionNoteResponse.model_rebuild()
|
||||
SubmissionResponse.model_rebuild()
|
||||
@@ -0,0 +1,75 @@
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from models.user import UserRole
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: EmailStr
|
||||
first_name: str
|
||||
last_name: str
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
password: str
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
role: Optional[UserRole] = None
|
||||
is_admin: Optional[bool] = None
|
||||
is_approved: Optional[bool] = None
|
||||
|
||||
|
||||
class UserResponse(UserBase):
|
||||
id: int
|
||||
role: UserRole
|
||||
is_admin: bool
|
||||
is_approved: bool
|
||||
avatar_url: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UserApproval(BaseModel):
|
||||
is_approved: bool
|
||||
|
||||
|
||||
class UserRoleUpdate(BaseModel):
|
||||
role: UserRole
|
||||
|
||||
|
||||
class UserAdminUpdate(BaseModel):
|
||||
is_admin: bool
|
||||
|
||||
|
||||
class UserAdminCreate(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
role: UserRole
|
||||
is_approved: bool = True
|
||||
is_admin: bool = False
|
||||
|
||||
|
||||
class UserAdminEdit(BaseModel):
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
email: Optional[EmailStr] = None
|
||||
role: Optional[UserRole] = None
|
||||
is_approved: Optional[bool] = None
|
||||
is_admin: Optional[bool] = None
|
||||
|
||||
|
||||
class UserPasswordReset(BaseModel):
|
||||
new_password: str
|
||||
|
||||
|
||||
class UserPasswordChange(BaseModel):
|
||||
current_password: str
|
||||
new_password: str
|
||||
Reference in New Issue
Block a user