---
url: "https://vueda.dev/v3/reference/api/py/vueda.core.permissions.md"
title: "core.permissions"
---

# core.permissions

## Overview

Object-level and row-level permission classes for CRUD and workflow integration.

## Classes

- [BaseRowLevelPermissions](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md)
- [DynamicObjectPermissions](/v3/reference/api/py/vueda.core.permissions/DynamicObjectPermissions.md)
- [ObjectPermissions](/v3/reference/api/py/vueda.core.permissions/ObjectPermissions.md)

## `check_action_permission` {#check_action_permission}

Whether ``request.user`` may perform ``action`` against ``instance`` -- or the model at
large, when ``instance`` is ``None`` -- through ``viewset``'s own configured permission
classes.

Checked as ``action`` over its own HTTP method, independently of whatever action the
surrounding response is actually for: a list response does not substitute list permission for
retrieve, and a write response does not substitute its own write permission for read.
``instance=None`` decides through ``has_permission`` alone, the model-level check, which lets
a matching workflow-state grant settle a model-level denial without scanning any row
(:func:`has_matching_state_grant`). A specific ``instance`` instead decides through
``viewset.check_object_permissions``, the same hook the endpoint itself calls to enforce the
request, so a viewset that overrides that hook to add its own object-level rules is honoured
here too, not only at the endpoint. It folds in row-level and per-object workflow-state rules
for that object either way.

``request`` is ``None`` when there is no request to authorize against (for example, schema
generation building metadata without a live requester); every caller of this function leaves
its result unfiltered in that case.

Caches its answer on ``viewset`` per ``(action, instance)`` for that viewset instance's own
lifetime -- one request -- so two callers asking the identical question (an object's own
``available_actions`` checking ``retrieve``, and ``history-list`` availability checking read
the same way) pay for one permission pass, not two. The cache keys on ``id(instance)``, since
a model instance is unhashable without a primary key and a composite primary key is not
reliably hashable either; it holds a reference to ``instance`` alongside its answer so that id
cannot be freed and reassigned to an unrelated object for as long as the cache entry lives,
which would otherwise alias a later, different row onto this row's cached answer.

### Signature {#check_action_permission-signature}

`check_action_permission(viewset, request, instance, action)`

#### Parameters {#check_action_permission-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="check_action_permission-param-viewset">viewset</span> |  | yes |  |  |
| <span id="check_action_permission-param-request">request</span> |  | yes |  |  |
| <span id="check_action_permission-param-instance">instance</span> |  | yes |  |  |
| <span id="check_action_permission-param-action">action</span> |  | yes |  |  |

#### Returns {#check_action_permission-returns}

`bool`

### Source {#check_action_permission-source}

`server/vueda/core/permissions.py:62`

## `filter_rows_for_user` {#filter_rows_for_user}

Apply row-level and workflow-aware queryset filters for the given ``perm_type``.
Calls ``RowLevelPermissions.check_queryset`` and, when the model has a workflow,
also annotates state permission info and calls ``check_queryset_workflow``.

This is the single row-visibility rule. A viewset reaches it through
``apply_row_level_filter``. History and the filter choices endpoint reach it directly, so an
event about a related row follows the same rule as reading that row, and a filter choice
comes only from a row the user may list.

### Signature {#filter_rows_for_user-signature}

`filter_rows_for_user(queryset, user, perm_type)`

#### Parameters {#filter_rows_for_user-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="filter_rows_for_user-param-queryset">queryset</span> |  | yes |  |  |
| <span id="filter_rows_for_user-param-user">user</span> |  | yes |  |  |
| <span id="filter_rows_for_user-param-perm_type">perm_type</span> |  | no | `'list'` |  |

### Source {#filter_rows_for_user-source}

`server/vueda/core/permissions.py:379`

## `has_matching_state_grant` {#has_matching_state_grant}

Whether a workflow state of ``model`` grants ``user`` any of ``required_permissions``.

This is the one model-scope deferral rule. A model-level denial stands unless a state rule
matches the caller's groups, a requested codename, the model's content type, and the model's
own workflow, and that rule grants rather than denies.

The answer says only that an object's state can still admit the request. It does not say that
any particular object does. Call it only from a path that goes on to make that per-object
decision, and let the object decision, including a matching deny, settle the request.

### Signature {#has_matching_state_grant-signature}

`has_matching_state_grant(model, user, required_permissions)`

#### Parameters {#has_matching_state_grant-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="has_matching_state_grant-param-model">model</span> |  | yes |  |  |
| <span id="has_matching_state_grant-param-user">user</span> |  | yes |  |  |
| <span id="has_matching_state_grant-param-required_permissions">required_permissions</span> |  | yes |  |  |

#### Returns {#has_matching_state_grant-returns}

`bool`

### Source {#has_matching_state_grant-source}

`server/vueda/core/permissions.py:27`

## `has_row_dependent_authorization` {#has_row_dependent_authorization}

Whether reading a row of ``model`` depends on the row rather than only on the model.

A model is row-dependent when it declares ``RowLevelPermissions`` or participates in a
configured workflow. History uses this to decide whether an event about a row that has since
been deleted can still be authorized. When the row is gone there is nothing left to evaluate,
so a row-dependent model fails closed.

### Signature {#has_row_dependent_authorization-signature}

`has_row_dependent_authorization(model)`

#### Parameters {#has_row_dependent_authorization-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="has_row_dependent_authorization-param-model">model</span> |  | yes |  |  |

#### Returns {#has_row_dependent_authorization-returns}

`bool`

### Source {#has_row_dependent_authorization-source}

`server/vueda/core/permissions.py:358`

## Source

`server/vueda/core/permissions.py`
