Source code for ferrosoft.apps.ferrobase.tables.crud
# Copyright (c) 2025 Ferrosoft GmbH. All rights reserved.
from dataclasses import dataclass
from django.utils.translation import gettext_lazy as _
from ferrosoft.apps.emiflow.widgets import EditDeleteActionsWidget
from ferrosoft.apps.ferrobase.tables.columns import WidgetColumn
from ferrosoft.apps.ferrobase.tables.table import Table
[docs]
@dataclass
class Action:
"""Extra header action exposed in the table template (label + URL)."""
url: str
label: str
[docs]
class CRUDTable(Table):
"""Table preconfigured with create/update/delete affordances.
Renders the "with add button" Bootstrap 5 template and an ``actions``
column containing per-row edit/delete buttons. The button URLs are
resolved through :meth:`get_update_action_url` and
:meth:`get_delete_action_url`, both of which subclasses are expected to
override when they need custom routes. Passing ``disable_update=True``
on construction hides the actions column and the add button — useful for
read-only views.
Template context exposes ``actions``, ``create_label``, ``create_url``
and ``disable_update`` in addition to the base table context.
"""
actions = WidgetColumn(
verbose_name=_("Actions"),
widget=EditDeleteActionsWidget(),
)
def __init__(self, *args, **kwargs):
"""Capture the CRUD-specific kwargs and wire URL resolvers."""
self._create_label = kwargs.pop(
"create_label", getattr(self.Meta, "create_label", _("Add"))
)
self._create_url = kwargs.pop(
"create_url", getattr(self.Meta, "create_url", None)
)
self._actions: list[Action] = kwargs.pop("actions", [])
self._disable_update = kwargs.pop("disable_update", False)
super().__init__(*args, **kwargs)
# Setting protected members here because of self binding issues when
# referencing in class body.
#
# noinspection PyProtectedMember
self.columns["actions"].column.widget._delete_url = self.get_delete_action_url
# noinspection PyProtectedMember
self.columns["actions"].column.widget._edit_url = self.get_update_action_url
[docs]
def before_render(self, request):
"""Hide the ``actions`` column when constructed with ``disable_update``."""
if self._disable_update and "actions" in self.columns:
self.columns.hide("actions")
[docs]
def get_delete_action_url(self, *args, **kwargs) -> str | None:
"""Return the delete URL for ``kwargs['record']`` or ``None`` (default)."""
return None
[docs]
def get_update_action_url(self, *args, **kwargs) -> str | None:
"""Return the row's ``get_absolute_url()`` if the model defines one."""
record = kwargs.get("record")
if hasattr(record, "get_absolute_url"):
return record.get_absolute_url()
return None
[docs]
def template_context(self, request):
"""Augment base template context with CRUD-specific keys."""
return super().template_context(request) | {
"actions": self._actions,
"create_label": self._create_label,
"create_url": self._create_url,
"disable_update": self._disable_update,
}