feat: add personal and group view ownership
This commit is contained in:
@@ -31,6 +31,10 @@ MODULE_VERSION = "0.1.0"
|
||||
|
||||
DEFINITION_READ_SCOPE = "views:definition:read"
|
||||
DEFINITION_WRITE_SCOPE = "views:definition:write"
|
||||
GROUP_DEFINITION_READ_SCOPE = "views:group_definition:read"
|
||||
GROUP_DEFINITION_WRITE_SCOPE = "views:group_definition:write"
|
||||
PERSONAL_DEFINITION_READ_SCOPE = "views:personal_definition:read"
|
||||
PERSONAL_DEFINITION_WRITE_SCOPE = "views:personal_definition:write"
|
||||
ASSIGNMENT_READ_SCOPE = "views:assignment:read"
|
||||
ASSIGNMENT_WRITE_SCOPE = "views:assignment:write"
|
||||
SELECTION_READ_SCOPE = "views:selection:read"
|
||||
@@ -72,6 +76,26 @@ PERMISSIONS = (
|
||||
"Manage tenant Views",
|
||||
"Create, revise, publish, and archive tenant View definitions.",
|
||||
),
|
||||
_permission(
|
||||
GROUP_DEFINITION_READ_SCOPE,
|
||||
"View group Views",
|
||||
"Read View definitions owned by groups the account belongs to.",
|
||||
),
|
||||
_permission(
|
||||
GROUP_DEFINITION_WRITE_SCOPE,
|
||||
"Manage group Views",
|
||||
"Create, revise, publish, and archive Views owned by the account's groups.",
|
||||
),
|
||||
_permission(
|
||||
PERSONAL_DEFINITION_READ_SCOPE,
|
||||
"View personal Views",
|
||||
"Read View definitions owned by the current account.",
|
||||
),
|
||||
_permission(
|
||||
PERSONAL_DEFINITION_WRITE_SCOPE,
|
||||
"Manage personal Views",
|
||||
"Create, revise, publish, and archive Views owned by the current account.",
|
||||
),
|
||||
_permission(
|
||||
ASSIGNMENT_READ_SCOPE,
|
||||
"View tenant View assignments",
|
||||
@@ -126,17 +150,39 @@ ROLE_TEMPLATES = (
|
||||
permissions=(
|
||||
DEFINITION_READ_SCOPE,
|
||||
DEFINITION_WRITE_SCOPE,
|
||||
GROUP_DEFINITION_READ_SCOPE,
|
||||
GROUP_DEFINITION_WRITE_SCOPE,
|
||||
PERSONAL_DEFINITION_READ_SCOPE,
|
||||
PERSONAL_DEFINITION_WRITE_SCOPE,
|
||||
ASSIGNMENT_READ_SCOPE,
|
||||
ASSIGNMENT_WRITE_SCOPE,
|
||||
SELECTION_READ_SCOPE,
|
||||
SELECTION_WRITE_SCOPE,
|
||||
),
|
||||
),
|
||||
RoleTemplate(
|
||||
slug="view_designer",
|
||||
name="View designer",
|
||||
description="Design personal Views and reusable Views for assigned groups.",
|
||||
permissions=(
|
||||
GROUP_DEFINITION_READ_SCOPE,
|
||||
GROUP_DEFINITION_WRITE_SCOPE,
|
||||
PERSONAL_DEFINITION_READ_SCOPE,
|
||||
PERSONAL_DEFINITION_WRITE_SCOPE,
|
||||
SELECTION_READ_SCOPE,
|
||||
SELECTION_WRITE_SCOPE,
|
||||
),
|
||||
),
|
||||
RoleTemplate(
|
||||
slug="view_user",
|
||||
name="View user",
|
||||
description="Use and select Views made available to the account.",
|
||||
permissions=(SELECTION_READ_SCOPE, SELECTION_WRITE_SCOPE),
|
||||
description="Use available Views and design Views for the current account.",
|
||||
permissions=(
|
||||
PERSONAL_DEFINITION_READ_SCOPE,
|
||||
PERSONAL_DEFINITION_WRITE_SCOPE,
|
||||
SELECTION_READ_SCOPE,
|
||||
SELECTION_WRITE_SCOPE,
|
||||
),
|
||||
default_authenticated=True,
|
||||
),
|
||||
)
|
||||
@@ -203,6 +249,14 @@ manifest = ModuleManifest(
|
||||
description="Edit tenant, group, and user Views and assignments.",
|
||||
order=30,
|
||||
),
|
||||
ViewSurface(
|
||||
id="views.settings.personal",
|
||||
module_id=MODULE_ID,
|
||||
kind="section",
|
||||
label="Personal and group Views",
|
||||
description="Design reusable Views owned by the account or its groups.",
|
||||
order=40,
|
||||
),
|
||||
),
|
||||
),
|
||||
route_factory=_router,
|
||||
@@ -269,6 +323,10 @@ __all__ = [
|
||||
"ASSIGNMENT_WRITE_SCOPE",
|
||||
"DEFINITION_READ_SCOPE",
|
||||
"DEFINITION_WRITE_SCOPE",
|
||||
"GROUP_DEFINITION_READ_SCOPE",
|
||||
"GROUP_DEFINITION_WRITE_SCOPE",
|
||||
"PERSONAL_DEFINITION_READ_SCOPE",
|
||||
"PERSONAL_DEFINITION_WRITE_SCOPE",
|
||||
"MODULE_ID",
|
||||
"MODULE_VERSION",
|
||||
"SELECTION_READ_SCOPE",
|
||||
|
||||
@@ -12,6 +12,10 @@ from govoplan_views.backend.manifest import (
|
||||
ASSIGNMENT_WRITE_SCOPE,
|
||||
DEFINITION_READ_SCOPE,
|
||||
DEFINITION_WRITE_SCOPE,
|
||||
GROUP_DEFINITION_READ_SCOPE,
|
||||
GROUP_DEFINITION_WRITE_SCOPE,
|
||||
PERSONAL_DEFINITION_READ_SCOPE,
|
||||
PERSONAL_DEFINITION_WRITE_SCOPE,
|
||||
SELECTION_READ_SCOPE,
|
||||
SELECTION_WRITE_SCOPE,
|
||||
SYSTEM_ASSIGNMENT_READ_SCOPE,
|
||||
@@ -82,7 +86,15 @@ def _require_any_scope(principal: ApiPrincipal, *scopes: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _require_definition_read(principal: ApiPrincipal, scope_type: str) -> None:
|
||||
def _has_any_scope(principal: ApiPrincipal, *scopes: str) -> bool:
|
||||
return any(has_scope(principal, scope) for scope in scopes)
|
||||
|
||||
|
||||
def _require_definition_read(
|
||||
principal: ApiPrincipal,
|
||||
scope_type: str,
|
||||
scope_id: str | None = None,
|
||||
) -> None:
|
||||
if scope_type == "system":
|
||||
_require_any_scope(
|
||||
principal,
|
||||
@@ -90,22 +102,98 @@ def _require_definition_read(principal: ApiPrincipal, scope_type: str) -> None:
|
||||
SYSTEM_DEFINITION_WRITE_SCOPE,
|
||||
)
|
||||
return
|
||||
_require_any_scope(
|
||||
principal,
|
||||
DEFINITION_READ_SCOPE,
|
||||
DEFINITION_WRITE_SCOPE,
|
||||
if scope_type == "tenant":
|
||||
_require_any_scope(
|
||||
principal,
|
||||
DEFINITION_READ_SCOPE,
|
||||
DEFINITION_WRITE_SCOPE,
|
||||
)
|
||||
return
|
||||
if _has_any_scope(principal, DEFINITION_READ_SCOPE, DEFINITION_WRITE_SCOPE):
|
||||
return
|
||||
if scope_type == "group":
|
||||
if not scope_id or scope_id not in principal.group_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Group Views can only be managed by members of that group.",
|
||||
)
|
||||
_require_any_scope(
|
||||
principal,
|
||||
GROUP_DEFINITION_READ_SCOPE,
|
||||
GROUP_DEFINITION_WRITE_SCOPE,
|
||||
)
|
||||
return
|
||||
if scope_type == "user":
|
||||
if scope_id != principal.account_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Personal Views can only be managed by their owner.",
|
||||
)
|
||||
_require_any_scope(
|
||||
principal,
|
||||
PERSONAL_DEFINITION_READ_SCOPE,
|
||||
PERSONAL_DEFINITION_WRITE_SCOPE,
|
||||
)
|
||||
return
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail=f"Unsupported View definition scope: {scope_type}",
|
||||
)
|
||||
|
||||
|
||||
def _require_definition_write(principal: ApiPrincipal, scope_type: str) -> None:
|
||||
_require_any_scope(
|
||||
principal,
|
||||
SYSTEM_DEFINITION_WRITE_SCOPE
|
||||
if scope_type == "system"
|
||||
else DEFINITION_WRITE_SCOPE,
|
||||
def _require_definition_write(
|
||||
principal: ApiPrincipal,
|
||||
scope_type: str,
|
||||
scope_id: str | None = None,
|
||||
) -> None:
|
||||
if scope_type == "system":
|
||||
_require_any_scope(principal, SYSTEM_DEFINITION_WRITE_SCOPE)
|
||||
return
|
||||
if scope_type == "tenant":
|
||||
_require_any_scope(principal, DEFINITION_WRITE_SCOPE)
|
||||
return
|
||||
if has_scope(principal, DEFINITION_WRITE_SCOPE):
|
||||
return
|
||||
if scope_type == "group":
|
||||
if not scope_id or scope_id not in principal.group_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Group Views can only be managed by members of that group.",
|
||||
)
|
||||
_require_any_scope(principal, GROUP_DEFINITION_WRITE_SCOPE)
|
||||
return
|
||||
if scope_type == "user":
|
||||
if scope_id != principal.account_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Personal Views can only be managed by their owner.",
|
||||
)
|
||||
_require_any_scope(principal, PERSONAL_DEFINITION_WRITE_SCOPE)
|
||||
return
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail=f"Unsupported View definition scope: {scope_type}",
|
||||
)
|
||||
|
||||
|
||||
def _definition_scope_id(
|
||||
principal: ApiPrincipal,
|
||||
scope_type: str,
|
||||
scope_id: str | None,
|
||||
) -> str | None:
|
||||
if scope_type in {"system", "tenant"}:
|
||||
return scope_id
|
||||
if scope_type == "user":
|
||||
return (scope_id or principal.account_id).strip()
|
||||
target_id = (scope_id or "").strip()
|
||||
if not target_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail="Group View definitions require a target id.",
|
||||
)
|
||||
return target_id
|
||||
|
||||
|
||||
def _require_assignment_read(principal: ApiPrincipal, scope_type: str) -> None:
|
||||
if scope_type == "system":
|
||||
_require_any_scope(
|
||||
@@ -279,6 +367,10 @@ def api_view_surfaces(
|
||||
principal,
|
||||
DEFINITION_READ_SCOPE,
|
||||
DEFINITION_WRITE_SCOPE,
|
||||
GROUP_DEFINITION_READ_SCOPE,
|
||||
GROUP_DEFINITION_WRITE_SCOPE,
|
||||
PERSONAL_DEFINITION_READ_SCOPE,
|
||||
PERSONAL_DEFINITION_WRITE_SCOPE,
|
||||
SYSTEM_DEFINITION_READ_SCOPE,
|
||||
SYSTEM_DEFINITION_WRITE_SCOPE,
|
||||
)
|
||||
@@ -313,16 +405,22 @@ def api_view_surfaces(
|
||||
|
||||
@router.get("/definitions", response_model=ViewDefinitionListResponse)
|
||||
def api_list_definitions(
|
||||
scope_type: str = Query(default="tenant", pattern="^(system|tenant)$"),
|
||||
scope_type: str = Query(
|
||||
default="tenant",
|
||||
pattern="^(system|tenant|group|user)$",
|
||||
),
|
||||
scope_id: str | None = Query(default=None, max_length=255),
|
||||
include_inherited: bool = True,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> ViewDefinitionListResponse:
|
||||
_require_definition_read(principal, scope_type)
|
||||
target_id = _definition_scope_id(principal, scope_type, scope_id)
|
||||
_require_definition_read(principal, scope_type, target_id)
|
||||
definitions = list_definitions(
|
||||
session,
|
||||
tenant_id=principal.tenant_id,
|
||||
scope_type=scope_type,
|
||||
scope_id=target_id,
|
||||
include_inherited=include_inherited,
|
||||
)
|
||||
return ViewDefinitionListResponse(
|
||||
@@ -330,7 +428,16 @@ def api_list_definitions(
|
||||
_definition_response(
|
||||
session,
|
||||
definition,
|
||||
readonly=(scope_type == "tenant" and definition.scope_type == "system"),
|
||||
readonly=(
|
||||
(scope_type == "tenant" and definition.scope_type == "system")
|
||||
or (
|
||||
scope_type in {"group", "user"}
|
||||
and (
|
||||
definition.scope_type != scope_type
|
||||
or definition.scope_id != target_id
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
for definition in definitions
|
||||
]
|
||||
@@ -347,12 +454,18 @@ def api_create_definition(
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> ViewDefinitionResponse:
|
||||
_require_definition_write(principal, payload.scope_type)
|
||||
target_id = _definition_scope_id(
|
||||
principal,
|
||||
payload.scope_type,
|
||||
payload.scope_id,
|
||||
)
|
||||
_require_definition_write(principal, payload.scope_type, target_id)
|
||||
try:
|
||||
definition = create_definition(
|
||||
session,
|
||||
tenant_id=principal.tenant_id,
|
||||
scope_type=payload.scope_type,
|
||||
scope_id=target_id,
|
||||
definition_key=payload.definition_key,
|
||||
name=payload.name,
|
||||
description=payload.description,
|
||||
@@ -391,7 +504,11 @@ def api_update_definition(
|
||||
tenant_id=principal.tenant_id,
|
||||
definition_id=definition_id,
|
||||
)
|
||||
_require_definition_write(principal, definition.scope_type)
|
||||
_require_definition_write(
|
||||
principal,
|
||||
definition.scope_type,
|
||||
definition.scope_id,
|
||||
)
|
||||
update_definition(
|
||||
session,
|
||||
definition,
|
||||
@@ -430,7 +547,11 @@ def api_list_revisions(
|
||||
tenant_id=principal.tenant_id,
|
||||
definition_id=definition_id,
|
||||
)
|
||||
_require_definition_read(principal, definition.scope_type)
|
||||
_require_definition_read(
|
||||
principal,
|
||||
definition.scope_type,
|
||||
definition.scope_id,
|
||||
)
|
||||
return [
|
||||
ViewRevisionResponse.model_validate(revision)
|
||||
for revision in definition_revisions(
|
||||
@@ -458,7 +579,11 @@ def api_create_revision(
|
||||
tenant_id=principal.tenant_id,
|
||||
definition_id=definition_id,
|
||||
)
|
||||
_require_definition_write(principal, definition.scope_type)
|
||||
_require_definition_write(
|
||||
principal,
|
||||
definition.scope_type,
|
||||
definition.scope_id,
|
||||
)
|
||||
revision = create_revision(
|
||||
session,
|
||||
definition,
|
||||
@@ -500,7 +625,11 @@ def api_publish_revision(
|
||||
tenant_id=principal.tenant_id,
|
||||
definition_id=definition_id,
|
||||
)
|
||||
_require_definition_write(principal, definition.scope_type)
|
||||
_require_definition_write(
|
||||
principal,
|
||||
definition.scope_type,
|
||||
definition.scope_id,
|
||||
)
|
||||
revision = get_revision(
|
||||
session,
|
||||
definition_id=definition.id,
|
||||
@@ -543,7 +672,11 @@ def api_archive_definition(
|
||||
tenant_id=principal.tenant_id,
|
||||
definition_id=definition_id,
|
||||
)
|
||||
_require_definition_write(principal, definition.scope_type)
|
||||
_require_definition_write(
|
||||
principal,
|
||||
definition.scope_type,
|
||||
definition.scope_id,
|
||||
)
|
||||
archive_definition(
|
||||
session,
|
||||
definition,
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Any, Literal
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
|
||||
ViewScopeType = Literal["system", "tenant"]
|
||||
ViewScopeType = Literal["system", "tenant", "group", "user"]
|
||||
AssignmentScopeType = Literal["system", "tenant", "group", "user"]
|
||||
AssignmentMode = Literal["available", "default", "required"]
|
||||
|
||||
@@ -68,6 +68,7 @@ class ViewDefinitionListResponse(BaseModel):
|
||||
|
||||
class ViewDefinitionCreateRequest(BaseModel):
|
||||
scope_type: ViewScopeType = "tenant"
|
||||
scope_id: str | None = Field(default=None, max_length=255)
|
||||
definition_key: str | None = Field(default=None, max_length=120)
|
||||
name: str = Field(min_length=1, max_length=200)
|
||||
description: str | None = Field(default=None, max_length=4000)
|
||||
|
||||
@@ -27,7 +27,7 @@ from govoplan_views.backend.db.models import (
|
||||
|
||||
ASSIGNMENT_MODES = frozenset({"available", "default", "required"})
|
||||
ASSIGNMENT_SCOPES = frozenset({"system", "tenant", "group", "user"})
|
||||
DEFINITION_SCOPES = frozenset({"system", "tenant"})
|
||||
DEFINITION_SCOPES = frozenset({"system", "tenant", "group", "user"})
|
||||
LOCKOUT_BASE_SURFACE_IDS = (
|
||||
"access.module",
|
||||
"access.nav.admin",
|
||||
@@ -91,11 +91,27 @@ def _scope_values(
|
||||
scope_type: str,
|
||||
*,
|
||||
tenant_id: str,
|
||||
scope_id: str | None = None,
|
||||
) -> tuple[str | None, str | None, str]:
|
||||
if scope_type == "system":
|
||||
if scope_id:
|
||||
raise ViewsValidationError(
|
||||
"System View definitions cannot declare a target id"
|
||||
)
|
||||
return None, None, "system"
|
||||
if scope_type == "tenant":
|
||||
if scope_id and scope_id != tenant_id:
|
||||
raise ViewsValidationError(
|
||||
"Tenant View definitions must target the active tenant"
|
||||
)
|
||||
return tenant_id, tenant_id, f"tenant:{tenant_id}"
|
||||
if scope_type in {"group", "user"}:
|
||||
target_id = (scope_id or "").strip()
|
||||
if not target_id:
|
||||
raise ViewsValidationError(
|
||||
f"{scope_type.title()} View definitions require a target id"
|
||||
)
|
||||
return tenant_id, target_id, f"{scope_type}:{tenant_id}:{target_id}"
|
||||
raise ViewsValidationError(f"Unsupported View definition scope: {scope_type}")
|
||||
|
||||
|
||||
@@ -158,6 +174,7 @@ def list_definitions(
|
||||
*,
|
||||
tenant_id: str,
|
||||
scope_type: str,
|
||||
scope_id: str | None = None,
|
||||
include_inherited: bool = True,
|
||||
) -> list[ViewDefinition]:
|
||||
if scope_type not in DEFINITION_SCOPES:
|
||||
@@ -168,12 +185,45 @@ def list_definitions(
|
||||
ViewDefinition.scope_type == "system",
|
||||
ViewDefinition.deleted_at.is_(None),
|
||||
)
|
||||
elif scope_type == "tenant":
|
||||
if not include_inherited:
|
||||
query = session.query(ViewDefinition).filter(
|
||||
ViewDefinition.tenant_id == tenant_id,
|
||||
ViewDefinition.scope_type == "tenant",
|
||||
ViewDefinition.deleted_at.is_(None),
|
||||
)
|
||||
else:
|
||||
query = _definition_query_for_actor(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
include_system=True,
|
||||
)
|
||||
else:
|
||||
query = _definition_query_for_actor(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
include_system=include_inherited,
|
||||
target_id = (scope_id or "").strip()
|
||||
if not target_id:
|
||||
raise ViewsValidationError(
|
||||
f"{scope_type.title()} View definition listings require a target id"
|
||||
)
|
||||
exact_target = (
|
||||
(ViewDefinition.tenant_id == tenant_id)
|
||||
& (ViewDefinition.scope_type == scope_type)
|
||||
& (ViewDefinition.scope_id == target_id)
|
||||
)
|
||||
query = session.query(ViewDefinition).filter(
|
||||
ViewDefinition.deleted_at.is_(None)
|
||||
)
|
||||
if include_inherited:
|
||||
query = query.filter(
|
||||
or_(
|
||||
exact_target,
|
||||
(ViewDefinition.tenant_id == tenant_id)
|
||||
& (ViewDefinition.scope_type == "tenant"),
|
||||
(ViewDefinition.tenant_id.is_(None))
|
||||
& (ViewDefinition.scope_type == "system"),
|
||||
)
|
||||
)
|
||||
else:
|
||||
query = query.filter(exact_target)
|
||||
return query.order_by(
|
||||
ViewDefinition.scope_type.asc(),
|
||||
ViewDefinition.name.asc(),
|
||||
@@ -291,6 +341,7 @@ def create_definition(
|
||||
*,
|
||||
tenant_id: str,
|
||||
scope_type: str,
|
||||
scope_id: str | None = None,
|
||||
definition_key: str | None,
|
||||
name: str,
|
||||
description: str | None,
|
||||
@@ -303,6 +354,7 @@ def create_definition(
|
||||
row_tenant_id, scope_id, scope_key = _scope_values(
|
||||
scope_type,
|
||||
tenant_id=tenant_id,
|
||||
scope_id=scope_id,
|
||||
)
|
||||
clean_name = name.strip()
|
||||
if not clean_name:
|
||||
@@ -489,9 +541,65 @@ def publish_revision(
|
||||
definition.updated_by = actor_id
|
||||
definition.updated_at = _now()
|
||||
session.flush()
|
||||
ensure_owner_available_assignment(
|
||||
session,
|
||||
definition,
|
||||
catalogue=catalogue,
|
||||
actor_id=actor_id,
|
||||
)
|
||||
return definition
|
||||
|
||||
|
||||
def ensure_owner_available_assignment(
|
||||
session: Session,
|
||||
definition: ViewDefinition,
|
||||
*,
|
||||
catalogue: Iterable[ViewSurface],
|
||||
actor_id: str | None,
|
||||
) -> ViewAssignment | None:
|
||||
if definition.scope_type not in {"group", "user"}:
|
||||
return None
|
||||
if not definition.tenant_id or not definition.scope_id:
|
||||
raise ViewsValidationError(
|
||||
"Owned View definitions require tenant and target identifiers"
|
||||
)
|
||||
target_key = f"{definition.scope_type}:{definition.tenant_id}:{definition.scope_id}"
|
||||
assignment = (
|
||||
session.query(ViewAssignment)
|
||||
.filter(
|
||||
ViewAssignment.target_key == target_key,
|
||||
ViewAssignment.definition_id == definition.id,
|
||||
ViewAssignment.mode == "available",
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if assignment is None:
|
||||
return create_assignment(
|
||||
session,
|
||||
tenant_id=definition.tenant_id,
|
||||
scope_type=definition.scope_type,
|
||||
scope_id=definition.scope_id,
|
||||
definition=definition,
|
||||
revision_id=None,
|
||||
mode="available",
|
||||
priority=0,
|
||||
is_active=True,
|
||||
metadata={"source": "definition_owner"},
|
||||
catalogue=catalogue,
|
||||
actor_id=actor_id,
|
||||
)
|
||||
assignment.revision_id = None
|
||||
assignment.is_active = True
|
||||
assignment.metadata_ = {
|
||||
**dict(assignment.metadata_ or {}),
|
||||
"source": "definition_owner",
|
||||
}
|
||||
assignment.updated_by = actor_id
|
||||
assignment.updated_at = _now()
|
||||
session.flush()
|
||||
return assignment
|
||||
|
||||
|
||||
def archive_definition(
|
||||
session: Session,
|
||||
definition: ViewDefinition,
|
||||
@@ -1181,6 +1289,7 @@ __all__ = [
|
||||
"get_assignment",
|
||||
"get_definition",
|
||||
"get_revision",
|
||||
"ensure_owner_available_assignment",
|
||||
"list_assignments",
|
||||
"list_definitions",
|
||||
"lockout_required_surface_ids",
|
||||
|
||||
Reference in New Issue
Block a user