Trash functionality

Introduction

The Trash feature enables both logical deletion and permanent deletion of objects. Combined with History, it provides full control over data management, including recovery and auditing of deleted records.

Model Integration

To enable trash management in your models, inherit from DeletedWithTrash. This abstract class is related to the Trash model and provides the necessary logic.

from djgentelella.models import DeletedWithTrash

class Customer(DeletedWithTrash):
    name = models.CharField(max_length=150)
    phone_number = models.CharField(max_length=150)
    email = models.EmailField()

    def __str__(self):
        return self.name

    def delete(self, using=None, keep_parents=False, *, hard=False, user=None, **kwargs):
        if self.is_deleted and not hard:
            return

        result = super().delete(
            using=using, keep_parents=keep_parents,
            hard=hard, user=user
        )
        return result

ViewSets and Deletion

There are two possible cases when overriding perform_destroy in your ViewSet:

Case 1: Without history

def perform_destroy(self, instance):
    instance.delete(user=self.request.user)

Case 2: With history

from djgentelella.history.api import BaseViewSetWithLogs
from djgentelella.history.utils import add_log, DELETION

class CustomerViewSet(BaseViewSetWithLogs):
    ...
    def perform_destroy(self, instance):
        # add log for history
        add_log(
            self.request.user,
            instance,
            DELETION,
            "customer",
            [],
            change_message=_("Deleted"),
        )
        # add user to deleted_by for trash
        instance.delete(user=self.request.user)

In both cases, you must override the perform_destroy method.

Forms

When using forms, you should exclude the is_deleted field:

from djgentelella.forms.forms import GTForm
import djgentelella.widgets.core as gtw

class CustomerForm(GTForm, forms.ModelForm):
    class Meta:
        model = Customer
        fields = "__all__"
        exclude = ["is_deleted"]
        widgets = {
            "name": gtw.TextInput,
            "email": gtw.EmailInput,
            "phone_number": gtw.PhoneNumberMaskInput,
        }

Template Example

Example HTML template for Trash:

<div class="card mt-2">
    <div class="card-body">
        <div class="card-title titles">
            <h2 class="text-center"> {% trans 'Trash' %} </h2>
        </div>

        <div class="row mt-3 ">
            <div class="col-12">
                <table class="table table-hover table-striped w-100 table-responsive" id="table-trash">
                </table>
            </div>
        </div>
    </div>
</div>

{# Modals actions #}
{% url "api-trash-list" as list_trash_url %}

{% trans 'Delete Instance' as delete_trash_tittle %}
{% include 'gentelella/blocks/modal_template_delete.html'
    with form=form_delete form_id="delete_trash_form"
    id="delete_trash_modal" title=delete_trash_tittle
    url=detail_trash_url %}

JavaScript Initialization

const trash_urls = {
    list_url: "{% url 'api-trash-list' %}",
    restore_url: "{% url "api-trash-restore"  0 %}",
    destroy_url: "{% url "api-trash-detail"  0 %}",
}

const modal_trash_ids = {
    destroy: "#delete_trash_modal",
}

const actions_trash = {
    table_actions: [],
    object_actions: [
        {
            'name': "restore",
            'action': 'restore',
            'in_action_column': true,
            'i_class': 'fa fa-undo',
            'method': 'POST',
            'title': gettext("Restore"),
            data_fn: function (data) {
                return data;
            }
        }
    ],
    title: 'Actions',
    className: "no-export-col"
}

const datatable_trash_inits = {
    columns: [
        {data: "id", name: "id", title: "ID", type: "string", visible: false},
        {
            data: "created_at",
            name: "created_at",
            title: gettext("Deleted Date"),
            visible: true,
            type: "date",
            dateformat: document.datetime_format
        },
        {
            data: "deleted_by",
            name: "deleted_by",
            title: gettext("Deleted by"),
            type: "readonly",
            visible: true,
        },
        {
            data: "object_repr",
            name: "object_repr",
            title: gettext("Description"),
            type: "readonly",
            visible: true,
        },
        {
            data: "model_name",
            name: "content_type__model",
            title: gettext("Model"),
            type: "readonly",
            visible: true,
        },
        {
            data: "actions",
            name: "actions",
            title: gettext("Actions"),
            type: "string",
            visible: true,
        },
    ],
    addfilter: true,
}

const trash_config = {
    datatable_element: "#table-trash",
    modal_ids: modal_trash_ids,
    actions: actions_trash,
    datatable_inits: datatable_trash_inits,
    add_filter: true,
    relation_render: {'field_autocomplete': 'text'},
    delete_display: data => {
        return `${gettext("Registration ID")}: ${data['id']} <br>
                ${gettext('Model')}: ${data["model_name"]} <br>
                <span class="text-danger">
                    ${gettext("This action will permanently delete the register, as well as all related data.")}
                </span>`;
    },
    icons: icons,
    urls: trash_urls,
}

const trash_crud = ObjectCRUD("trashcrudobj", trash_config)

trash_crud.restore = function (data) {
    const url = trash_urls.restore_url.replace("0", data.id)

    $.ajax({
        url: url, type: "POST", data: {}, headers: {
            "X-CSRFToken": getCookie('csrftoken'),
        }, success: function (response) {
            if (response.result) {
                Swal.fire({
                    icon: 'success',
                    title: gettext('Success'),
                    text: gettext(response.detail),
                    confirmButtonText: gettext('Accept'),
                })

                ocrud.datatable.ajax.reload() // only for example
                trash_crud.datatable.ajax.reload()
            } else {
                window.location.reload();
            }
        }, error: function (xhr, status, error) {
            console.error("Error executing the API:", error);

            Swal.fire({
                icon: 'error',
                title: gettext('Error'),
                text: gettext("Sorry, an error occurred."),
                confirmButtonText: gettext('Accept'),
            })
        }
    });
}

trash_crud.init();

Object Managers

When inheriting from DeletedWithTrash, three different managers are available to handle objects:

  • objects = ObjectManager() → Returns only non-deleted objects.

  • objects_with_deleted = AllObjectsManager() → Returns all objects, including deleted.

  • objects_deleted_only = DeletedObjectsManager() → Returns only deleted objects.

This allows flexible querying and management of your application data.