Skip to content

angee.graphql.deletion ​

GraphQL delete-confirmation preview built on Django's deletion collector.

DeletePreviewGroup ​

python
@strawberry.type
class DeletePreviewGroup()

A count of affected rows for one Django model.

label ​

Human-readable plural model label.

count ​

Number of affected rows for this model.

DeletePreviewNode ​

python
@strawberry.type
class DeletePreviewNode()

One node in a cascade deletion preview tree.

label ​

Human-readable model or group label.

object_label ​

Human-readable object display label.

object_id ​

Public object id when the node represents a concrete row.

children ​

Child nodes below this preview node.

from_row ​

python
@classmethod
def from_row(cls, instance: models.Model) -> DeletePreviewNode

Return a childless leaf node for one deleted row.

from_group ​

python
@classmethod
def from_group(cls, model: type[models.Model],
               rows: _PreviewRows) -> DeletePreviewNode

Return a grouped child node summarizing deleted rows of one model.

from_target ​

python
@classmethod
def from_target(
        cls, instance: models.Model,
        groups: dict[type[models.Model], _PreviewRows]) -> DeletePreviewNode

Return the root node (the deletion target) with its grouped children.

DeletePreview ​

python
@strawberry.type
class DeletePreview()

Cascade forecast for deleting one Django model instance.

total_deleted_count ​

Total number of rows Django would delete.

deleted ​

Rows Django would delete.

updated ​

Rows Django would update because of on_delete behavior.

blocked ​

Rows whose on_delete behavior blocks deletion.

has_blockers ​

Whether any related rows block deletion (i.e. blocked is non-empty).

root ​

Rooted tree of rows Django would delete; deleted counts include this root row.

deleted_instance ​

Deleted row returned internally to mutation envelopes, never exposed in SDL.

from_instance ​

python
@classmethod
def from_instance(cls,
                  instance: models.Model,
                  actor: Any | None = None) -> DeletePreview

Return Django's cascade forecast for instance.

Callers should run previews inside the same transaction as the eventual delete so fast-delete counts and visible rows share one database snapshot.

from_counts ​

python
@classmethod
def from_counts(cls, target: models.Model,
                counts: Mapping[type[models.Model], int]) -> DeletePreview

Return a delete forecast whose non-root rows are known by count, not collected.

:meth:from_instance runs Django's Collector, which materializes every related row — including the on_delete=SET_NULL rows it would only update. When a delete is really a purge that removes those rows through a separate bulk scoped queryset.delete() (a messaging channel's threads and messages FK the shared integrate.Integration parent with SET_NULL, so the collector would forecast them as updated and load all of them), the purge owner counts the scope with .count() and passes the totals here instead. This builds the same preview shape with no per-row materialization — a fixed number of queries regardless of how large the purged scope is.

target is the tree root: it counts as one deleted row of its own model, and each of its multi-table-inheritance parents (target._meta.parents — e.g. a channel's Integration row) as one more, since deleting an MTI child deletes its parent rows too. counts names the additional purged models and their totals — the deletion scope including the CASCADE subtrees (for a channel: its threads and messages plus their parts, reactions, participants, followers, activities, notifications, edges, stars, and tracking values) — so summing them equals the rows the delete removes. Any type(target) entry in counts is ignored so a caller that re-counts the root cannot double it against the root's own +1.

Unlike :meth:from_instance, counts is caller-trusted and unscoped: the purge owner computes it elevated, so the preview reflects the true scope regardless of the actor's REBAC row visibility. The count-based path assumes no blockers — a channel purge has none today — so it takes the has_blockers=False branch; a blocker only surfaces if the confirm delete actually raises, whose owner rebuilds the preview through :meth:blocked_from_error.

blocked_from_error ​

python
@classmethod
def blocked_from_error(
        cls, target: models.Model,
        error: ProtectedError | RestrictedError) -> DeletePreview

Return a preview surfacing the rows that blocked a purge's confirm delete.

The count-based :meth:from_counts path assumes no blockers. If a consumer addon ever PROTECT\s or RESTRICT\s a reverse FK to target, the confirm delete raises instead of orphaning; the purge owner catches it and calls this so the surface reports the blockers as a blocked preview (mirroring :func:delete_by_public_id), never a raw 500. Nothing was deleted — the purge transaction rolled back — so the deleted/updated counts are empty and only blocked is populated, straight from the offending rows the error carries.

delete_by_public_id ​

python
def delete_by_public_id(model: type[models.Model],
                        public_id: str,
                        *,
                        confirm: bool = False,
                        queryset: models.QuerySet[models.Model] | None = None,
                        before_delete: Callable[[models.Model], None]
                        | None = None,
                        reason: str | None = None) -> DeletePreview

Preview, then optionally delete, one public-id-addressed model row.

The caller owns authorization. Passing reason elevates the lookup/delete under system_context for admin/action surfaces whose permission class has already gated the request actor.

attach_delete_preview_metadata ​

python
def attach_delete_preview_metadata(
        surface: type[_SurfaceT],
        *,
        model: type[models.Model],
        node: type,
        field: str,
        model_label: str | None = None,
        public_id_field: str = PUBLIC_ID_FIELD_NAME) -> type[_SurfaceT]

Attach resource metadata for one authored cascade-preview mutation.

_PreviewRows ​

python
@dataclass(slots=True)
class _PreviewRows()

Access-scoped visible/count-only row state for one preview tree group.

add ​

python
def add(*, total_count: int, visible_count: int,
        visible_rows: Iterable[models.Model]) -> None

Merge one collector source into this model group.

by_model ​

python
@classmethod
def by_model(cls, root: models.Model, collector: Collector,
             fast_deletes: tuple[_FastDelete, ...],
             actor: Any | None) -> dict[type[models.Model], _PreviewRows]

Return deleted tree rows grouped by model, excluding root.

from_collected ​

python
@classmethod
def from_collected(cls, root: models.Model, model: type[models.Model],
                   rows: Iterable[models.Model],
                   actor: Any | None) -> _PreviewRows

Return access-scoped preview rows from collector-materialized rows.

from_fast_delete ​

python
@classmethod
def from_fast_delete(cls, queryset: models.QuerySet[models.Model],
                     total_count: int, actor: Any | None) -> _PreviewRows

Return access-scoped preview rows from one fast-delete queryset.

Released under the AGPL-3.0 License.