Files
rengine/shared/models/project.py
T
Yogesh OjhaandClaude Opus 5 ace6192237 fix(projects): the name check ran on responses and broke the project list
The name validator added in 048abbf3 went on ProjectBase, which
ProjectRead inherits, so it ran when the API serialised a project as
well as when it accepted one. A project saved with a blank name before
that rule existed then failed response validation, and GET /projects
returned a 500 for every caller — the sidebar showed "Loading..." and no
project could be selected.

A rule about what a client may send belongs on the request model. It now
sits on ProjectCreate, so stored rows written before the rule still read
back. The equivalent validators on engines, contexts and schedules were
already request-only and are unaffected; all thirteen list endpoints
were checked.

The scan launch also had its own wording for an unreadable target value;
it now uses the same sentence as every other target path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6LcH8uUddjJbKrahf34ys
2026-09-07 05:38:33 +05:30

49 lines
1.3 KiB
Python

import uuid
from datetime import datetime
from functools import partial
from pydantic import BaseModel, field_validator
from sqlmodel import Field, SQLModel
from shared.utils.datetime import utc_now
from shared.utils.validation import clean_name
class ProjectBase(SQLModel):
name: str = Field(max_length=50)
class Project(ProjectBase, table=True):
__tablename__ = "projects"
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True, index=True)
slug: str = Field(max_length=100, unique=True, index=True)
description: str | None = Field(default=None, max_length=1000)
label: str | None = Field(default=None, max_length=50)
is_active: bool = Field(default=True)
created_at: datetime = Field(default_factory=utc_now)
created_by: uuid.UUID = Field(foreign_key="users.id")
class ProjectCreate(ProjectBase):
description: str | None = None
label: str | None = None
# on the request only: a stored name from before this rule must still read back
_validate_name = field_validator("name")(partial(clean_name, max_len=50))
class ProjectRead(ProjectBase):
id: uuid.UUID
slug: str
description: str | None = None
label: str | None = None
is_active: bool
created_at: datetime
created_by: uuid.UUID
class ProjectSummary(BaseModel):
project: ProjectRead
stats: dict[str, int]