import contextlib from dataclasses import dataclass from django.core.exceptions import FieldError from django.core.paginator import Paginator from django.db.models import Model from django.http import HttpRequest from django.http import HttpResponse from django.urls import reverse from zen_queries import render @dataclass class Row: """ A row in a table. """ data: dict[str, str] actions: list[dict[str, str]] @dataclass class RowAction: """ An action that can be performed on a row in a table. """ label: str url_name: str url_kwargs: dict[str, str] def render(self, obj) -> dict[str, str]: """ Render the action as a dictionary for the given object. """ url = reverse( self.url_name, kwargs={key: getattr(obj, value) for key, value in self.url_kwargs.items()}, ) return {"label": self.label, "url": url} def render_list( request: HttpRequest, objects: list["Model"], columns: list[tuple[str, str]], row_actions: list[RowAction] = None, list_actions: list[tuple[str, str]] = None, paginate_by: int = None, ) -> HttpResponse: """ Render a list of objects with a table. """ # TODO: List actions total_count = len(objects) order_by = request.GET.get("order_by") if order_by: with contextlib.suppress(FieldError): objects = objects.order_by(order_by) if paginate_by: paginator = Paginator(object_list=objects, per_page=paginate_by) page = paginator.get_page(request.GET.get("page")) objects = page.object_list rows = [] for obj in objects: row = Row( data={column: getattr(obj, column[0]) for column in columns}, actions=[action.render(obj) for action in row_actions], ) rows.append(row) context = { "rows": rows, "columns": columns, "row_actions": row_actions, "list_actions": list_actions, "total_count": total_count, "order_by": order_by, } if paginate_by: context |= { "page": page, "is_paginated": True, } return render( request=request, template_name="utils/list.html", context=context, )