Fix route: use model_fields_set to pass _UNSET when role_id not sent, None when explicitly null

This commit is contained in:
2026-07-03 20:43:55 +00:00
parent d7691b3d70
commit 997d60138a
+19 -5
View File
@@ -13,7 +13,7 @@ from app.core.db import get_db
from app.core.notifications import create_notification
from app.deps import get_current_user, require_admin
from app.schemas.user import UserCreate, UserUpdate
from app.services.user_service import user_service
from app.services.user_service import user_service, _UNSET
router = APIRouter(prefix="/api/v1/users", tags=["users"])
@@ -21,7 +21,7 @@ router = APIRouter(prefix="/api/v1/users", tags=["users"])
def _parse_role_id(raw: str | None) -> uuid.UUID | None:
"""Convert a string body value into a UUID or None.
Empty string and None are both treated as "no role_id".
Empty string and None are both treated as "clear role_id".
"""
if raw is None or raw == "":
return None
@@ -138,7 +138,12 @@ async def update_user(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_admin),
):
"""Update a user (admin only)."""
"""Update a user (admin only).
Uses ``model_fields_set`` to detect whether ``role_id`` was explicitly
present in the request body (even if sent as ``null``). This allows
the caller to clear the FK by sending ``role_id: null``.
"""
tenant_id = uuid.UUID(current_user["tenant_id"])
acting_user_id = uuid.UUID(current_user["user_id"])
try:
@@ -148,17 +153,26 @@ async def update_user(
400, detail={"detail": "Invalid user_id", "code": "invalid_id"}
) from None
# Determine if role_id was explicitly sent (Pydantic v2)
role_id_sent = "role_id" in body.model_fields_set
changes: dict[str, Any] = {}
if body.name is not None:
changes["name"] = body.name
if body.role is not None:
changes["role"] = body.role
if body.role_id is not None:
if role_id_sent:
changes["role_id"] = body.role_id
if body.is_active is not None:
changes["is_active"] = body.is_active
role_id = _parse_role_id(body.role_id)
# Pass _UNSET sentinel when role_id was not in the request body
# so the service leaves the existing value untouched.
role_id: uuid.UUID | None | Any
if role_id_sent:
role_id = _parse_role_id(body.role_id)
else:
role_id = _UNSET
user = await user_service.update_user(
db,