Add per-task-type submission configuration with client-side movie spec checks
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).
This commit is contained in:
@@ -16,6 +16,7 @@ from schemas.project import (
|
||||
ProjectTechnicalSpecs, DeliveryMovieSpec, DEFAULT_DELIVERY_MOVIE_SPECS,
|
||||
ProjectSettings, ProjectSettingsUpdate, DEFAULT_ASSET_TASKS, DEFAULT_SHOT_TASKS
|
||||
)
|
||||
from schemas.submission_config import ProjectSubmissionConfig, SubmissionTypeConfig
|
||||
from utils.auth import get_current_user, require_role, get_current_user_from_token
|
||||
|
||||
router = APIRouter()
|
||||
@@ -1077,6 +1078,87 @@ async def delete_custom_task_type(
|
||||
return _build_all_task_types_response(db_project)
|
||||
|
||||
|
||||
# Submission Configuration Endpoints
|
||||
|
||||
@router.get("/{project_id}/submission-config", response_model=ProjectSubmissionConfig)
|
||||
async def get_project_submission_config(
|
||||
project_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user_with_db)
|
||||
):
|
||||
"""Get the project's per-task-type submission configuration"""
|
||||
project = db.query(Project).filter(Project.id == project_id).first()
|
||||
|
||||
if not project:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Project not found"
|
||||
)
|
||||
|
||||
if current_user.role == UserRole.ARTIST:
|
||||
member = db.query(ProjectMember).filter(
|
||||
ProjectMember.project_id == project_id,
|
||||
ProjectMember.user_id == current_user.id
|
||||
).first()
|
||||
if not member:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to this project"
|
||||
)
|
||||
|
||||
config_data = project.submission_config_by_task_type
|
||||
if isinstance(config_data, str):
|
||||
try:
|
||||
config_data = json.loads(config_data)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
config_data = {}
|
||||
|
||||
return ProjectSubmissionConfig(config_by_task_type=config_data or {})
|
||||
|
||||
|
||||
@router.put("/{project_id}/submission-config", response_model=ProjectSubmissionConfig)
|
||||
async def update_project_submission_config(
|
||||
project_id: int,
|
||||
submission_config: ProjectSubmissionConfig,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_coordinator_or_admin)
|
||||
):
|
||||
"""Replace the project's per-task-type submission configuration"""
|
||||
db_project = db.query(Project).filter(Project.id == project_id).first()
|
||||
|
||||
if not db_project:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Project not found"
|
||||
)
|
||||
|
||||
all_types_response = _build_all_task_types_response(db_project)
|
||||
valid_task_types = set(all_types_response.asset_task_types) | set(all_types_response.shot_task_types)
|
||||
for task_type in submission_config.config_by_task_type.keys():
|
||||
if task_type not in valid_task_types:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail=f"'{task_type}' is not a valid task type for this project"
|
||||
)
|
||||
|
||||
db_project.submission_config_by_task_type = {
|
||||
task_type: config.dict()
|
||||
for task_type, config in submission_config.config_by_task_type.items()
|
||||
}
|
||||
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(db_project)
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to update submission configuration"
|
||||
)
|
||||
|
||||
return ProjectSubmissionConfig(config_by_task_type=db_project.submission_config_by_task_type or {})
|
||||
|
||||
|
||||
# Project Thumbnail Management Endpoints
|
||||
|
||||
@router.post("/{project_id}/thumbnail", status_code=status.HTTP_201_CREATED)
|
||||
|
||||
Reference in New Issue
Block a user