angee.graphql.deletion
GraphQL delete-confirmation preview built on Django's deletion collector.
DeletePreviewGroup
@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
@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
@classmethod
def from_row(cls, instance: models.Model) -> DeletePreviewNodeReturn a childless leaf node for one deleted row.
from_group
@classmethod
def from_group(cls, model: type[models.Model],
rows: _PreviewRows) -> DeletePreviewNodeReturn a grouped child node summarizing deleted rows of one model.
from_target
@classmethod
def from_target(
cls, instance: models.Model,
groups: dict[type[models.Model], _PreviewRows]) -> DeletePreviewNodeReturn the root node (the deletion target) with its grouped children.
DeletePreview
@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
@classmethod
def from_instance(cls,
instance: models.Model,
actor: Any | None = None) -> DeletePreviewReturn 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
@classmethod
def from_counts(cls, target: models.Model,
counts: Mapping[type[models.Model], int]) -> DeletePreviewReturn 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
@classmethod
def blocked_from_error(
cls, target: models.Model,
error: ProtectedError | RestrictedError) -> DeletePreviewReturn 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
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) -> DeletePreviewPreview, 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
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
@dataclass(slots=True)
class _PreviewRows()Access-scoped visible/count-only row state for one preview tree group.
add
def add(*, total_count: int, visible_count: int,
visible_rows: Iterable[models.Model]) -> NoneMerge one collector source into this model group.
by_model
@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
@classmethod
def from_collected(cls, root: models.Model, model: type[models.Model],
rows: Iterable[models.Model],
actor: Any | None) -> _PreviewRowsReturn access-scoped preview rows from collector-materialized rows.
from_fast_delete
@classmethod
def from_fast_delete(cls, queryset: models.QuerySet[models.Model],
total_count: int, actor: Any | None) -> _PreviewRowsReturn access-scoped preview rows from one fast-delete queryset.