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

# VuedaViewSet

## Overview

Full CRUD ViewSet for VUEDA models. Extends DRF's ``ModelViewSet`` with:

- Multipart saves that carry JSON values and nested files (``NestedMultipartMixin``)
- Flex-fields expansion (``FlexFieldsMixin``)
- Query-parameter validation against filter and serializer fields (``NoExtraFieldsForViewSetMixin``)
- Row-level and workflow-aware list filtering (``ListRowLevelViewSetMixin``)
- Bulk delete with dry-run support
- Override ``destroy_validation`` to add pre-delete business rules.
- Override ``get_warnings_for_object`` (from ``WarningConfirmationMixin``) to gate
  single and bulk ``destroy`` (and ``activate``/``deactivate`` when
  ``DeactivateActionViewSetMixin`` is mixed in) behind a 409 confirmation with the same rule
  for both; override ``get_warnings`` instead if bulk needs its own logic.

## `apply_object_permission_filter` {#apply_object_permission_filter}

Keep only objects the current request can access at object-permission level.

Every refusal ``check_object_permissions`` can raise drops the object, the same set
``vueda.core.permissions.check_action_permission`` treats as a refusal. That includes
``Http404``, which DRF's ``DjangoObjectPermissions`` raises for a write when the user also
cannot read the object, and Django's ``PermissionDenied`` from a viewset override.

### Signature {#apply_object_permission_filter-signature}

`apply_object_permission_filter(self, queryset)`

#### Parameters {#apply_object_permission_filter-parameters}

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

### Source {#apply_object_permission_filter-source}

`server/vueda/core/viewsets/__init__.py:1697`

## `create` {#create}

### Signature {#create-signature}

`create(self, request, args, kwargs)`

#### Parameters {#create-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="create-param-request">request</span> |  | yes |  |  |
| <span id="create-param-args">args</span> |  | no |  |  |
| <span id="create-param-kwargs">kwargs</span> |  | no |  |  |

### Source {#create-source}

`server/vueda/core/viewsets/__init__.py:1681`

## `destroy` {#destroy}

Delete one or more objects.

### Signature {#destroy-signature}

`destroy(self, request, kwargs)`

#### Parameters {#destroy-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="destroy-param-request">request</span> |  | yes |  |  |
| <span id="destroy-param-kwargs">kwargs</span> |  | no |  |  |

### Source {#destroy-source}

`server/vueda/core/viewsets/__init__.py:1748`

## `destroy_validation` {#destroy_validation}

Override to validate objects before deletion. Raise ``VuedaValidationError``
to prevent deletion. Called for both single-object and bulk-delete requests.

### Signature {#destroy_validation-signature}

`destroy_validation(self, objs)`

#### Parameters {#destroy_validation-parameters}

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

### Source {#destroy_validation-source}

`server/vueda/core/viewsets/__init__.py:1690`

## `get_allowed_extra_actions` {#get_allowed_extra_actions}

Override this function to change if a user is allowed to do a certain action.

``request`` is ``None`` when model metadata or ``available_actions`` is built from a
serializer context without a request. An override must handle that case before reading
``request.user``. With no request, this method offers every extra action, the same answer
:func:`vueda.core.permissions.check_action_permission` gives with no request.

``history_list`` is additionally gated on read authorization here, checked the same way an
object's own ``retrieve`` already is (:meth:`_read_permitted`). ``history_list`` belongs to
``workflow_object_permission_actions`` by default (see that attribute above), so a
requester whose read comes only from a matching workflow-state grant is offered
``history-list`` here the same way a requester whose read comes from a model-level
permission already is, and both agree with what the history endpoint's own enforcement
answers for the same row.

### Signature {#get_allowed_extra_actions-signature}

`get_allowed_extra_actions(self, request, instance)`

#### Parameters {#get_allowed_extra_actions-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="get_allowed_extra_actions-param-request">request</span> |  | yes |  |  |
| <span id="get_allowed_extra_actions-param-instance">instance</span> |  | no | `None` |  |

### Source {#get_allowed_extra_actions-source}

`server/vueda/core/viewsets/__init__.py:1771`

## `get_bulk_queryset` {#get_bulk_queryset}

Return the rows that a bulk request's ``pks`` body names, after permission filtering.

Each entry converts through the model's primary key field, as
:class:`~vueda.core.serializers.PrimaryKeyListSerializer` describes. The rows then pass
through :meth:`apply_row_level_filter` for ``perm_type`` and through
:meth:`apply_object_permission_filter`, the checks that a single-object request makes.

A key that names no row raises :class:`~vueda.core.exceptions.VuedaValidationError` with
"Object with pk=... does not exist." for that key. A key that names a row that the request
may not change raises the same error, so the response does not reveal that the row exists.

Returns the queryset and a dict that maps each converted key to the value that the request
sent. Key errors use the sent value, because a composite key converts to a tuple, which
cannot be a JSON object key.

### Signature {#get_bulk_queryset-signature}

`get_bulk_queryset(self, request, perm_type)`

#### Parameters {#get_bulk_queryset-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="get_bulk_queryset-param-request">request</span> |  | yes |  |  |
| <span id="get_bulk_queryset-param-perm_type">perm_type</span> |  | yes |  |  |

### Source {#get_bulk_queryset-source}

`server/vueda/core/viewsets/__init__.py:1715`

## `get_extra_actions` {#get_extra_actions}

Drop the history endpoint for a model that records no history.

Every consumer of the extra actions reads this: the router that builds the routes, the
permitted-action list the client renders, and the model-info metadata. Gating here means an
opted-out model has no history route, no control, and no schema entry, rather than a route
that answers with an error.

### Signature {#get_extra_actions-signature}

`get_extra_actions(cls)`

#### Parameters {#get_extra_actions-parameters}

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

### Source {#get_extra_actions-source}

`server/vueda/core/viewsets/__init__.py:1625`

## `get_object` {#get_object}

Override this function to convert the pk kwarg to a list if the model has a composite primary key.

### Signature {#get_object-signature}

`get_object(self)`

#### Parameters {#get_object-parameters}

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

### Source {#get_object-source}

`server/vueda/core/viewsets/__init__.py:1814`

## `get_queryset` {#get_queryset}

Annotate ``formatted_name`` and, for ``list``/``retrieve``, apply the ``select_related``/
``prefetch_related`` plan :func:`build_prefetch_plan` derives from what the request's ``?e=``
actually expanded, so an expanded response's query count does not grow with the number of
rows returned. See :func:`build_prefetch_plan` for why ``?f=``/``?om=`` play no part in this.

:func:`apply_prefetch_plan` applies the plan. A relation that this queryset already
prefetches with a ``Prefetch`` queryset of its own (typically one that the viewset's own
``queryset``/``get_queryset()`` hand-optimized) keeps the viewset's ``Prefetch`` when a
request expands it. A relation that this queryset prefetches without a queryset loads through
the plan's ``Prefetch``. No join replaces the objects that a ``Prefetch`` queryset loads.

The expands nested under a relation keep their plan when this queryset lists the
``Prefetch`` that loads the relation, to-one or to-many. They load after the viewset's
``Prefetch`` at one query per relation for each request. The viewset's ``Prefetch`` queryset
is rebuilt without the joins that pass through a relation that a nested expand prefetches,
and keeps its filters, annotations, and ordering. A prefetch inside the queryset of the
viewset's ``Prefetch`` loads its relation while that queryset runs, and the expands nested
under that relation load one query per row.

### Signature {#get_queryset-signature}

`get_queryset(self)`

#### Parameters {#get_queryset-parameters}

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

### Source {#get_queryset-source}

`server/vueda/core/viewsets/__init__.py:1837`

## `history_list` {#history_list}

Return this object's history as the user actions behind it.

A page is a page of actions. One action that wrote several rows stays whole, because the
grouping and the visibility filter both run in the database before the paginator sees
anything.

### Signature {#history_list-signature}

`history_list(self, request, pk)`

#### Parameters {#history_list-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="history_list-param-request">request</span> |  | yes |  |  |
| <span id="history_list-param-pk">pk</span> |  | no | `None` |  |

### Source {#history_list-source}

`server/vueda/core/viewsets/__init__.py:1642`

## `initial` {#initial}

Runs anything that needs to occur prior to calling the method handler.

### Signature {#initial-signature}

`initial(self, request, args, kwargs)`

#### Parameters {#initial-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="initial-param-request">request</span> |  | yes |  |  |
| <span id="initial-param-args">args</span> |  | no |  |  |
| <span id="initial-param-kwargs">kwargs</span> |  | no |  |  |

### Source {#initial-source}

`server/vueda/core/viewsets/__init__.py:1616`

## `update` {#update}

### Signature {#update-signature}

`update(self, request, args, kwargs)`

#### Parameters {#update-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| self |  | yes |  |  |
| <span id="update-param-request">request</span> |  | yes |  |  |
| <span id="update-param-args">args</span> |  | no |  |  |
| <span id="update-param-kwargs">kwargs</span> |  | no |  |  |

### Source {#update-source}

`server/vueda/core/viewsets/__init__.py:1685`

## `detail_args` {#detail_args}

## `nulls_ordering` {#nulls_ordering}

Nulls placement per field name, ``"first"`` or ``"last"``, applied wherever that field is sorted.

``VuedaOrderingFilter`` reads it. Keys are ``__``-joined field paths, as in ``ordering``.

## `nulls_ordering_flip` {#nulls_ordering_flip}

Field names in ``nulls_ordering`` whose placement flips when the field is sorted descending.

## `workflow_object_permission_actions` {#workflow_object_permission_actions}

## Source

`server/vueda/core/viewsets/__init__.py:1574`
