import contextlib from dataclasses import dataclass from typing import Any from django.contrib.sites.shortcuts import get_current_site 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 queries_disabled from zen_queries import render as zen_queries_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, entity_name: str, entity_name_plural: str, objects: list["Model"], columns: list[tuple[str, str]], row_actions: list[RowAction] | None = None, list_actions: list[tuple[str, str]] | None = None, paginate_by: int | None = 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: with queries_disabled(): 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, "entity_name": entity_name, "entity_name_plural": entity_name_plural, } if paginate_by: context |= { "page": page, "is_paginated": True, } return render( request=request, template_name="utils/list.html", context=context, ) def base_context(request: HttpRequest) -> dict[str, Any]: """Return a base context for all views.""" return {"site": get_current_site(request)} def render(request, template_name, context=None): """Render a template with a base context.""" if context is None: context = {} context = base_context(request) | context # Make sure to fetch all permissions before rendering the template # otherwise django-zen-queries will complain about database queries. request.user.get_all_permissions() return zen_queries_render(request, template_name, context)