---
url: "https://vueda.dev/v3/reference/permissions.md"
title: "Permissions"
---

# Permissions

This page lists the permission codenames that VUEDA checks and the codename that each request requires. It also lists the [Row-Level Permissions](/v3/reference/glossary.md#row-level-permissions) hook signatures, the status code for each refused request, and the supported [Permission Mapping](/v3/reference/glossary.md#permission-mapping) values. [Permission Model](/v3/core-concepts/permission-model.md) describes the [Permission Layers](/v3/reference/glossary.md#permission-layers) and why each refusal takes the form it does. Every check on this page is server enforcement. [Authorization vs UI Semantics](/v3/core-concepts/authorization-vs-ui-semantics.md) describes how the client's visible actions relate to server enforcement.

## Codenames

VUEDA models declare five actions in [`default_permissions`](/v3/reference/api/py/vueda.core.models/BaseModelMeta.md#default_permissions). Importing [core.patch\_django](/v3/reference/api/py/vueda.core.patch_django.md) adds `list` to every other model whose `Meta` does not declare its own `default_permissions`.

Each codename takes the form `<app_label>.<action>_<model_name>`.

| Action   | Codename for `myapp.Widget` | Grants                                      |
| -------- | --------------------------- | ------------------------------------------- |
| `create` | `myapp.create_widget`       | `POST` to the list route                    |
| `read`   | `myapp.read_widget`         | Retrieve, and `GET` on any extra action     |
| `update` | `myapp.update_widget`       | `PUT` and `PATCH`                           |
| `delete` | `myapp.delete_widget`       | Single-object `DELETE` and bulk delete      |
| `list`   | `myapp.list_widget`         | `GET` on the list route (the `list` action) |

The table uses the default mapping. Under Django's names, the codenames change as [Permission Name Mapping](/v3/reference/permissions.md#permission-name-mapping) lists.

## Required Codename per Request

A permission class maps each request method to a codename. [`ObjectPermissions`](/v3/reference/api/py/vueda.core.permissions/ObjectPermissions.md) is the default class for VUEDA viewsets, and its [`perms_map`](/v3/reference/api/py/vueda.core.permissions/ObjectPermissions.md#perms_map) holds the mapping.

| Method         | `ObjectPermissions`, default mapping                          | `ObjectPermissions`, Django's names |
| -------------- | ------------------------------------------------------------- | ----------------------------------- |
| `GET`, `HEAD`  | `list_` for the `list` action; `read_` for every other action | `view_` for every action            |
| `POST`         | `create_`                                                     | `add_`                              |
| `PUT`, `PATCH` | `update_`                                                     | `change_`                           |
| `DELETE`       | `delete_`                                                     | `delete_`                           |
| `OPTIONS`      | None                                                          | None                                |

Endpoints that name their model in the URL use [`DynamicObjectPermissions`](/v3/reference/api/py/vueda.core.permissions/DynamicObjectPermissions.md) or its subclass [`WorkflowObjectPermissions`](/v3/reference/api/py/vueda.workflow.permissions/WorkflowObjectPermissions.md). These are the workflow endpoints that address a model's data and [workflow state history](/v3/reference/api/rest/vueda.history/vueda.history_workflow_state_history_list.md). Their [`crudl_perms_map`](/v3/reference/api/py/vueda.core.permissions/DynamicObjectPermissions.md#crudl_perms_map) names an action, which the class resolves through the mapping on each check.

| Method        | `DynamicObjectPermissions` action |
| ------------- | --------------------------------- |
| `GET`, `HEAD` | `read`                            |
| `POST`        | `create`                          |
| `PUT`         | `update`                          |
| `PATCH`       | `read`                            |
| `DELETE`      | `delete`                          |
| `OPTIONS`     | None                              |

The [field choices](/v3/reference/api/rest/vueda.info/vueda.info_model_info_choices_list.md) and [filter choices](/v3/reference/api/rest/vueda.info/vueda.info_model_info_filter_choices_list.md) endpoints check the `read` codename on the model. When the options are another model's rows, they also check the `list` codename on that model. Both resolve through the mapping on each check.

Workflow endpoints also check `vueda_workflow.read_workflow`, except object state and `permitted_transitions`. [Workflow as a Permission Overlay](/v3/core-concepts/workflow-permission-overlay.md) lists the gates that each workflow endpoint applies.

## Row-Level Hooks

A model opts in by declaring an inner `RowLevelPermissions` class that subclasses [BaseRowLevelPermissions](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md). Each hook is a `classmethod`, and the base class returns `None` from all four. [Row-Level Permission Filtering](/v3/core-concepts/row-level-permission-filtering.md) describes which requests run each hook, and [Permission Model](/v3/core-concepts/permission-model.md#permission-authority-layers) describes the order of the instance hooks among the layers.

### Signatures

| Hook                                                                                                                 | Signature                                                                                                          | Runs                                                                                                     |
| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| [`check_queryset`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_queryset)                   | `check_queryset(cls, queryset, perm, user, perm_type)`                                                             | For `list`, bulk delete, and [Model History](/v3/reference/glossary.md#model-history) events about the model's rows                         |
| [`check_queryset_workflow`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_queryset_workflow) | `check_queryset_workflow(cls, queryset, perm, user, perm_type, state_denied_annotation, state_granted_annotation)` | After `check_queryset` and the [State Permission](/v3/reference/glossary.md#state-permission) filter, on a [Workflow-Enabled Model](/v3/reference/glossary.md#workflow-enabled-model) only |
| [`check_instance`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_instance)                   | `check_instance(cls, model, obj, perm, user, perm_type)`                                                           | In every [Object-Scope Check](/v3/reference/glossary.md#object-scope-check), except when a state rule denied the codename                        |
| [`check_instance_workflow`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_instance_workflow) | `check_instance_workflow(cls, model, obj, perm, user, perm_type, grant_or_deny)`                                   | Last in every object-scope check on a workflow-enabled model, whatever the state rules returned          |

### Arguments

| Argument                                                                                                                                    | Hooks                     | Value                                                                                                                                                                                              |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`queryset`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_queryset-param-queryset)                                          | Queryset hooks            | The rows so far. `check_queryset_workflow` receives them after the state filter, with the two state annotations added.                                                                             |
| [`perm`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_queryset-param-perm)                                                  | All                       | The codename being checked, as `<app_label>.<codename>`, after mapping. Instance hooks also receive the workflow and transition permission codenames that workflow checks test against the object. |
| [`user`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_queryset-param-user)                                                  | All                       | The requesting user.                                                                                                                                                                               |
| [`perm_type`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_queryset-param-perm_type) (queryset hooks)                       | Queryset hooks            | The caller's action name, before mapping: `"list"` for `list`, `"delete"` for bulk delete, `"read"` for history.                                                                                   |
| [`perm_type`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_instance-param-perm_type) (instance hooks)                       | Instance hooks            | The codename's text before its first underscore, after mapping: `read` for `myapp.read_widget`. Under Django's names it is `view`, `add`, or `change` for those actions.                           |
| [`model`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_instance-param-model)                                                | Instance hooks            | The object's class.                                                                                                                                                                                |
| [`obj`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_instance-param-obj)                                                    | Instance hooks            | The object being checked.                                                                                                                                                                          |
| [`state_denied_annotation`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_queryset_workflow-param-state_denied_annotation)   | `check_queryset_workflow` | The name of a boolean annotation: `True` when a state rule for the row's current state denies the codename to one of the user's groups.                                                            |
| [`state_granted_annotation`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_queryset_workflow-param-state_granted_annotation) | `check_queryset_workflow` | The name of a boolean annotation: `True` when such a rule grants it.                                                                                                                               |
| [`grant_or_deny`](/v3/reference/api/py/vueda.core.permissions/BaseRowLevelPermissions.md#check_instance_workflow-param-grant_or_deny)                       | `check_instance_workflow` | The state rules' result for the object: `True` (grant), `False` (deny), or `None` (no rule matched).                                                                                               |

Under the default mapping, the two `perm_type` values agree. They differ when the mapping renames an action: for `list` under Django's names, the queryset hooks receive `"list"` and `perm` ends in `view_<model_name>`.

### Return Values

| Return  | Queryset hooks                                                                                               | Instance hooks                                        |
| ------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
| `Q`     | Filters the queryset by the condition.                                                                       | Not accepted.                                         |
| `True`  | Leaves the queryset unchanged.                                                                               | Grants, replacing the decision of the earlier layers. |
| `False` | Returns an empty queryset. From `check_queryset`, the state filter and `check_queryset_workflow` do not run. | Denies, replacing the decision of the earlier layers. |
| `None`  | Leaves the queryset unchanged.                                                                               | Keeps the decision of the earlier layers.             |

A `check_instance_workflow` result is final, a state deny included. `check_queryset_workflow` can only remove rows, so it cannot restore a row that the state filter removed.

## Status Codes

The status code that a refused request returns depends on the check that refused it. [Permission Model](/v3/core-concepts/permission-model.md#why-refused-requests-fail-differently) describes why.

| Request                                                                                                                | Refusal                                                                                                                                                                                            | Status | Response                                                                                                               |
| ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- |
| Any viewset request, including `list` and `create`                                                                     | The [Model-Scope Check](/v3/reference/glossary.md#model-scope-check) fails, and no state grant defers it ([Model-Scope Deferral](/v3/reference/glossary.md#model-scope-deferral))                                                                                                   | `403`  | `detail` message                                                                                                       |
| `list`                                                                                                                 | Row filtering removes every row                                                                                                                                                                    | `200`  | Empty `results`, `totalRecords` of `0`                                                                                 |
| Retrieve                                                                                                               | The object check denies `read`                                                                                                                                                                     | `404`  | The same response as a missing object                                                                                  |
| `update`, `partial_update`, single-object `DELETE`                                                                     | The object check denies the action, and the user can read the object                                                                                                                               | `403`  | `detail` message                                                                                                       |
| `update`, `partial_update`, single-object `DELETE`                                                                     | The object check denies the action, and the user cannot read the object                                                                                                                            | `404`  | The same response as a missing object                                                                                  |
| Bulk delete ([`destroy`](/v3/reference/api/py/vueda.core.viewsets/VuedaViewSet.md#destroy) with `pks`)                        | A pk is missing, removed by row filtering, or refused by its object check                                                                                                                          | `400`  | One error per such pk, keyed by pk: `"Object with pk=<pk> does not exist."` Nothing is deleted.                        |
| Field choices, filter choices                                                                                          | The user lacks `read_` on the model, or `list_` on the related model                                                                                                                               | `403`  | `detail` message                                                                                                       |
| Workflow endpoints other than object state and `permitted_transitions`                                                 | The user lacks `vueda_workflow.read_workflow`                                                                                                                                                      | `403`  | `detail` message                                                                                                       |
| Object state, object transitions, single-object execute transition, workflow state history                             | The object check denies `read`                                                                                                                                                                     | `403`  | `detail` message                                                                                                       |
| [Bulk execute transition](/v3/reference/api/rest/vueda.workflow/vueda.workflow_workflows_execute_transition_partial_update.md)  | An `object_ids` entry is missing, or the user cannot read it                                                                                                                                       | `404`  | The same response for both. No transition is applied.                                                                  |
| [Execute transition](/v3/reference/api/py/vueda.workflow.viewsets/WorkflowViewSet.md#execute_transition)                      | The user lacks a [Workflow Permission](/v3/reference/glossary.md#workflow-permission) or [Transition Permission](/v3/reference/glossary.md#transition-permission); the transition code is unknown; the transition does not leave the current state; another request holds the row lock | `400`  | Validation error. A bulk request keys the errors by object id and applies no transition.                               |
| [`permitted_transitions`](/v3/reference/api/rest/vueda.workflow/vueda.workflow_workflows_permitted_transitions_list.md) | The model does not enable `class Vueda.Workflow`                                                                                                                                                   | `200`  | `[]`, for any user with `read_` on the model                                                                           |
| `permitted_transitions`                                                                                                | The user lacks `read_` on the model, `vueda_workflow.read_workflow`, or the workflow's workflow permissions                                                                                        | `403`  | `detail` message                                                                                                       |
| Any workflow check                                                                                                     | The model enables `class Vueda.Workflow` but has no workflow definition                                                                                                                            | `500`  | [`WorkflowNotConfiguredError`](/v3/reference/api/py/vueda.workflow.exceptions/WorkflowNotConfiguredError.md) message in `detail` |

[Workflow as a Permission Overlay](/v3/core-concepts/workflow-permission-overlay.md) describes the transition gates and the workflow endpoint rules.

## Permission Name Mapping

`PERMISSION_NAMES_MAPPING` renames permission actions (see [Permission Mapping](/v3/reference/glossary.md#permission-mapping)). [Map Django and VUEDA Permission Names](/v3/guides/permission-name-mapping.md) gives the steps to set it and check the result. VUEDA supports two values. The [`check_permission_names_mapping`](/v3/reference/api/py/vueda.core.checks.md#check_permission_names_mapping) system check reports any other value as `vueda_core.E018`.

The default, from [core.default\_settings.get\_defaults](/v3/reference/api/py/vueda.core.default_settings.md#get_defaults):

```python
PERMISSION_NAMES_MAPPING = {
    "add": "create",
    "change": "update",
    "view": "read",
}
```

Django's names, for a database whose permission rows and group assignments already use `add`, `change`, and `view`:

```python
PERMISSION_NAMES_MAPPING = {
    "create": "add",
    "list": "view",
    "read": "view",
    "update": "change",
}
```

| Consumer                                                          | Reads the mapping                     | Follows                                                                                                                                                     |
| ----------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Permission row creation (`migrate`)                               | When it runs                          | Any key                                                                                                                                                     |
| `ObjectPermissions.perms_map`                                     | Once, when `patch_django` is imported | Only the values `add`, `change`, and `view`, each when it is not also a key. A `view` value makes every `GET` require `view_`, for `list` and detail alike. |
| Row-level filtering, `DynamicObjectPermissions`, choice endpoints | When each check runs                  | Any key                                                                                                                                                     |

Map `list` to `view` whenever `read` maps to `view`. The viewsets then check the same codename as row-level filtering and the choice endpoints. Other mappings, such as `"delete": "remove"` or `"list": "browse"`, create permission rows that `ObjectPermissions` never checks.
