---
url: "https://vueda.dev/v3/reference/api/py/vueda.workflow.models/WorkflowModelMethods.md"
title: "WorkflowModelMethods"
---

# WorkflowModelMethods

## Overview

Model-level utility methods for objects with workflow.

A model does not subclass this directly. ``class Vueda.Workflow`` with ``enabled = True`` makes
the workflow app append it to the model's bases once Django prepares the model, so it sits last
in the method resolution order. A method the model or any of its bases defines takes precedence,
and an override can still call ``super()`` to reach the default here.

## `allow_transition` {#allow_transition}

Check if transition is allowed for this object.
return falsy or a string will raise a InvalidTransitionError exception in apply_transition

Resolves only ``transition``. The cost does not grow with the number of other transitions
leaving the current state. Use ``available_transitions`` when the permitted set itself is
what is wanted.

### Signature {#allow_transition-signature}

`allow_transition(self, transition, user)`

#### Parameters {#allow_transition-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="allow_transition-param-transition">transition</span> | `vueda.workflow.models.Transition` | yes |  |  |
| <span id="allow_transition-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |

#### Returns {#allow_transition-returns}

`bool | str`

### Source {#allow_transition-source}

`server/vueda/workflow/models.py:950`

## `apply_checked_transition` {#apply_checked_transition}

Write an already-authorized transition (as returned by ``check_transition``) without
re-checking permissions or availability.

Returns the target state and the object state's new revision, which is ``None`` when the
object has no state row to record.

### Signature {#apply_checked_transition-signature}

`apply_checked_transition(self, transition, user, dry_run)`

#### Parameters {#apply_checked_transition-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="apply_checked_transition-param-transition">transition</span> | `vueda.workflow.models.Transition` | yes |  |  |
| <span id="apply_checked_transition-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |
| <span id="apply_checked_transition-param-dry_run">dry_run</span> | `bool` | no | `False` |  |

#### Returns {#apply_checked_transition-returns}

`tuple[vueda.workflow.models.State, str | None]`

### Source {#apply_checked_transition-source}

`server/vueda/workflow/models.py:1045`

## `apply_transition` {#apply_transition}

Apply a transition to the object.

### Signature {#apply_transition-signature}

`apply_transition(self, transition_code, user, dry_run)`

#### Parameters {#apply_transition-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="apply_transition-param-transition_code">transition_code</span> | `str` | yes |  |  |
| <span id="apply_transition-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |
| <span id="apply_transition-param-dry_run">dry_run</span> | `bool` | no | `False` |  |

#### Returns {#apply_transition-returns}

`tuple[vueda.workflow.models.State, str | None]`

### Source {#apply_transition-source}

`server/vueda/workflow/models.py:1060`

## `available_transitions` {#available_transitions}

Returns available transitions for this object.

Empty when ``user`` does not hold the workflow's configured permissions for this object
(see ``has_workflow_permission``), including when the workflow configures none.

The whole pass runs inside one ``cached_workflow_state()`` block, so the object's workflow,
current state, and state rules are read once rather than once per candidate transition.

### Signature {#available_transitions-signature}

`available_transitions(self, user)`

#### Parameters {#available_transitions-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="available_transitions-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |

#### Returns {#available_transitions-returns}

`django.db.models.query.QuerySet`

### Source {#available_transitions-source}

`server/vueda/workflow/models.py:747`

## `available_transitions_for` {#available_transitions_for}

Returns available transitions for a list of objects.

A transition is available when at least one of ``objs`` sits in one of its source states and
``user`` may take it on that object. This matches the source-state filter, which admits a
transition leaving any of the objects' states rather than all of them.

``check_transition_permission`` resolves ``user.has_perms(perms, obj=...)``, so the answer
depends on the object it receives. This classmethod therefore loads the concrete instances
and asks each candidate object rather than asking the model class.

### Signature {#available_transitions_for-signature}

`available_transitions_for(cls, objs, user)`

#### Parameters {#available_transitions_for-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| cls |  | yes |  |  |
| <span id="available_transitions_for-param-objs">objs</span> | `list['WorkflowModelMethods'] \| list[int] \| django.db.models.query.QuerySet` | yes |  |  |
| <span id="available_transitions_for-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |

#### Returns {#available_transitions_for-returns}

`django.db.models.query.QuerySet`

### Source {#available_transitions_for-source}

`server/vueda/workflow/models.py:783`

## `cached_workflow_state` {#cached_workflow_state}

Hold this object's ``workflow`` and ``object_state`` for the duration of the block.

One authorization pass reads both repeatedly: ``check_state_permission`` resolves the
current state for every permission it is asked about, and ``VuedaUserMixin.has_perm``
reads the workflow to decide whether state rules apply at all. Without this, each read
is a fresh query.

The cache is scoped to a block rather than to the instance on purpose. ``execute_transition``
checks a transition, takes a row lock, and checks again against the locked row, and that
second check has to observe any state written in between. Nested blocks reuse the
outermost cache and leave it to the outermost block to clear.

### Signature {#cached_workflow_state-signature}

`cached_workflow_state(self)`

#### Parameters {#cached_workflow_state-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |

#### Returns {#cached_workflow_state-returns}

`collections.abc.Iterator[None]`

### Source {#cached_workflow_state-source}

`server/vueda/workflow/models.py:694`

## `check_state_permission` {#check_state_permission}

Check whether the object's current state grants or denies ``perm`` for any of ``groups``.
Returns ``True`` (grant), ``False`` (deny), or ``None`` (no state rule applies).
When multiple rules match, deny takes precedence over grant.

``caller`` is the user ``groups`` belongs to. Passing it lets one ``cached_workflow_state()``
block resolve that caller's rules once instead of once per permission. Without it every call
resolves, which is what an uncached caller gets.

### Signature {#check_state_permission-signature}

`check_state_permission(self, perm, groups, caller)`

#### Parameters {#check_state_permission-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="check_state_permission-param-perm">perm</span> | `str` | yes |  |  |
| <span id="check_state_permission-param-groups">groups</span> | `collections.abc.Iterable[str] \| collections.abc.Iterable[int] \| django.db.models.query.QuerySet` | yes |  |  |
| <span id="check_state_permission-param-caller">caller</span> | `django.db.models.base.Model \| None` | no | `None` |  |

#### Returns {#check_state_permission-returns}

`bool | None`

### Source {#check_state_permission-source}

`server/vueda/workflow/models.py:869`

## `check_transition` {#check_transition}

Validate that ``transition_code`` can be applied by ``user``, without writing anything.

Performs the same permission and ``allow_transition`` checks as ``apply_transition``
(raising the same exceptions), and resolves the effective ``user`` (falling back to the
acting user the history middleware records on the action, then the system user, exactly as
``apply_transition`` does). Callers that need to gate a transition on warnings (see ``get_transition_warnings``)
before writing should call this first, then ``apply_checked_transition``.

### Signature {#check_transition-signature}

`check_transition(self, transition_code, user)`

#### Parameters {#check_transition-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="check_transition-param-transition_code">transition_code</span> | `str` | yes |  |  |
| <span id="check_transition-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |

#### Returns {#check_transition-returns}

`tuple[vueda.workflow.models.Transition, doc_app.models.User]`

### Source {#check_transition-source}

`server/vueda/workflow/models.py:1009`

## `check_transition_permission` {#check_transition_permission}

user as None means superuser, pass django's AnonymousUser if you want to check for anonymous user.

### Signature {#check_transition_permission-signature}

`check_transition_permission(self, transition, user)`

#### Parameters {#check_transition_permission-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="check_transition_permission-param-transition">transition</span> | `vueda.workflow.models.Transition` | yes |  |  |
| <span id="check_transition_permission-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |

#### Returns {#check_transition_permission-returns}

`bool`

### Source {#check_transition_permission-source}

`server/vueda/workflow/models.py:931`

## `check_workflow_permission` {#check_workflow_permission}

Return True when ``user`` holds the workflow's configured permissions, else raise ``PermissionDenied``.

See ``has_workflow_permission`` for the decision.

### Signature {#check_workflow_permission-signature}

`check_workflow_permission(cls, user, obj)`

#### Parameters {#check_workflow_permission-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| cls |  | yes |  |  |
| <span id="check_workflow_permission-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |
| <span id="check_workflow_permission-param-obj">obj</span> | `django.db.models.base.Model \| None` | no | `None` |  |

#### Returns {#check_workflow_permission-returns}

`bool`

### Source {#check_workflow_permission-source}

`server/vueda/workflow/models.py:857`

## `create_object_state` {#create_object_state}

Create a workflow object for this object.

### Signature {#create_object_state-signature}

`create_object_state(self)`

#### Parameters {#create_object_state-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |

### Source {#create_object_state-source}

`server/vueda/workflow/models.py:677`

## `fast_available_transitions` {#fast_available_transitions}

Returns available transitions for this object without permission checks.

### Signature {#fast_available_transitions-signature}

`fast_available_transitions(self)`

#### Parameters {#fast_available_transitions-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |

#### Returns {#fast_available_transitions-returns}

`django.db.models.query.QuerySet`

### Source {#fast_available_transitions-source}

`server/vueda/workflow/models.py:768`

## `fast_transition` {#fast_transition}

Apply a transition without permission checks. Raises ``InvalidTransitionError``
if the transition is not available from the current state, unless the
``TransitionSource`` is marked ``ignored``, in which case ``on_transition_ignored`` is called.

### Signature {#fast_transition-signature}

`fast_transition(self, transition_code)`

#### Parameters {#fast_transition-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="fast_transition-param-transition_code">transition_code</span> | `str` | yes |  |  |

### Source {#fast_transition-source}

`server/vueda/workflow/models.py:1069`

## `get_content_type` {#get_content_type}

Return the ``ContentType`` for this model class. Result is cached by Django.

### Signature {#get_content_type-signature}

`get_content_type(cls)`

#### Parameters {#get_content_type-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| cls |  | yes |  |  |

#### Returns {#get_content_type-returns}

`django.contrib.contenttypes.models.ContentType`

### Source {#get_content_type-source}

`server/vueda/workflow/models.py:688`

## `get_transition` {#get_transition}

Return the ``Transition`` with the given code in this object's workflow. Raises ``Transition.DoesNotExist``
if not found.

### Signature {#get_transition-signature}

`get_transition(self, transition_code)`

#### Parameters {#get_transition-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="get_transition-param-transition_code">transition_code</span> | `str` | yes |  |  |

#### Returns {#get_transition-returns}

`vueda.workflow.models.Transition`

### Source {#get_transition-source}

`server/vueda/workflow/models.py:986`

## `get_transition_warnings` {#get_transition_warnings}

Hook returning advisory warnings for a transition, consulted before it is written.

Override to report warnings that should gate the transition behind confirmation (HTTP 409)
without denying it outright the way ``allow_transition`` does. Return the aggregate
``{field: [messages]}`` warnings dict (use ``"non_field_errors"`` for warnings not tied to a
field). The default returns ``{}``, meaning no confirmation is required. See
``vueda.core.exceptions.gate_warnings`` for how the caller turns this into a 409.

### Signature {#get_transition_warnings-signature}

`get_transition_warnings(self, transition, user)`

#### Parameters {#get_transition_warnings-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="get_transition_warnings-param-transition">transition</span> | `vueda.workflow.models.Transition` | yes |  |  |
| <span id="get_transition_warnings-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |

#### Returns {#get_transition_warnings-returns}

`dict`

### Source {#get_transition_warnings-source}

`server/vueda/workflow/models.py:974`

## `has_workflow_permission` {#has_workflow_permission}

Whether ``user`` holds the workflow's configured permissions.

The one decision behind ``check_workflow_permission``, ``available_transitions``, and
``available_transitions_for``, so the endpoints and the ``valid_transitions`` field agree.

``user`` as None means programmatic use; pass Django's AnonymousUser to check an anonymous
request. ``obj`` scopes the check to one object, so an object permission backend decides
the configured workflow permissions the way it decides the target model's own. A workflow
with no configured permissions denies every user, superusers included.

A state rule is not a substitute for a configured workflow permission. State rules apply
where a concrete object supplies the state, which is the object permission decision this
check passes ``obj`` to, not a reason to skip the check.

### Signature {#has_workflow_permission-signature}

`has_workflow_permission(cls, user, obj)`

#### Parameters {#has_workflow_permission-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| cls |  | yes |  |  |
| <span id="has_workflow_permission-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |
| <span id="has_workflow_permission-param-obj">obj</span> | `django.db.models.base.Model \| None` | no | `None` |  |

#### Returns {#has_workflow_permission-returns}

`bool`

### Source {#has_workflow_permission-source}

`server/vueda/workflow/models.py:828`

## `on_transition` {#on_transition}

Override this method to add custom logic on transition.

### Signature {#on_transition-signature}

`on_transition(self, transition, user, dry_run)`

#### Parameters {#on_transition-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="on_transition-param-transition">transition</span> | `vueda.workflow.models.Transition` | yes |  |  |
| <span id="on_transition-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |
| <span id="on_transition-param-dry_run">dry_run</span> | `bool` | no | `False` |  |

### Source {#on_transition-source}

`server/vueda/workflow/models.py:1099`

## `on_transition_ignored` {#on_transition_ignored}

Override this method to add custom logic when a transition is intentionally ignored.

### Signature {#on_transition_ignored-signature}

`on_transition_ignored(self, transition, user)`

#### Parameters {#on_transition_ignored-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="on_transition_ignored-param-transition">transition</span> | `vueda.workflow.models.Transition` | yes |  |  |
| <span id="on_transition_ignored-param-user">user</span> | `doc_app.models.User \| None` | no | `None` |  |

### Source {#on_transition_ignored-source}

`server/vueda/workflow/models.py:1104`

## `should_ignore_transition_from_state` {#should_ignore_transition_from_state}

Return True if the transition should be treated as a no-op from the current state.

### Signature {#should_ignore_transition_from_state-signature}

`should_ignore_transition_from_state(self, transition)`

#### Parameters {#should_ignore_transition_from_state-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="should_ignore_transition_from_state-param-transition">transition</span> | `vueda.workflow.models.Transition` | yes |  |  |

#### Returns {#should_ignore_transition_from_state-returns}

`bool`

### Source {#should_ignore_transition_from_state-source}

`server/vueda/workflow/models.py:999`

## `update_object_state` {#update_object_state}

Write ``state`` as this object's current state.

The write joins the open action. ``apply_checked_transition`` and ``fast_transition`` open one
named after the transition when no action is open.

### Signature {#update_object_state-signature}

`update_object_state(self, state)`

#### Parameters {#update_object_state-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="update_object_state-param-state">state</span> |  | yes |  |  |

### Source {#update_object_state-source}

`server/vueda/workflow/models.py:1088`

## `object_state` {#object_state}

Return the ``ObjectState`` record for this instance, or ``None`` if not yet created.

### Source {#object_state-source}

`server/vueda/workflow/models.py:729`

## `workflow` {#workflow}

Return the ``Workflow`` configured for this model. Raises ``WorkflowNotConfiguredError`` if none exists.

### Source {#workflow-source}

`server/vueda/workflow/models.py:718`

## `workflow_state` {#workflow_state}

Return the current ``State`` for this instance, or ``None`` if no state exists.

### Source {#workflow_state-source}

`server/vueda/workflow/models.py:741`

## Source

`server/vueda/workflow/models.py:664`
